diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46ee666aa..0116131c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: uses: actions/checkout@v4 - name: Execute tests - run: dotnet test --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj + run: dotnet test --framework net8.0 --configuration Release Source/MQTTnet.Tests/MQTTnet.Tests.csproj sign: needs: build diff --git a/MQTTnet.sln.DotSettings b/MQTTnet.sln.DotSettings index 962baa61f..64de52630 100644 --- a/MQTTnet.sln.DotSettings +++ b/MQTTnet.sln.DotSettings @@ -1,4 +1,8 @@  + DO_NOT_SHOW + SUGGESTION + DO_NOT_SHOW + DO_NOT_SHOW Required Required Required diff --git a/Samples/Client/Client_Connection_Samples.cs b/Samples/Client/Client_Connection_Samples.cs index 8d716f500..32a8784c7 100644 --- a/Samples/Client/Client_Connection_Samples.cs +++ b/Samples/Client/Client_Connection_Samples.cs @@ -413,7 +413,7 @@ public static async Task Reconnect_Using_Event() if (e.ClientWasConnected) { // Use the current options as the new options. - await mqttClient.ConnectAsync(mqttClient.Options); + await mqttClient.ConnectAsync(mqttClientOptions); } }; diff --git a/Samples/Client/Client_Publish_Samples.cs b/Samples/Client/Client_Publish_Samples.cs index 16cc67c78..957baf958 100644 --- a/Samples/Client/Client_Publish_Samples.cs +++ b/Samples/Client/Client_Publish_Samples.cs @@ -23,25 +23,23 @@ public static async Task Publish_Application_Message() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("broker.hivemq.com") - .Build(); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("broker.hivemq.com") + .Build(); - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("samples/temperature/living_room") - .WithPayload("19.5") - .Build(); + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("samples/temperature/living_room") + .WithPayload("19.5") + .Build(); - await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); + await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); - await mqttClient.DisconnectAsync(); + await mqttClient.DisconnectAsync(); - Console.WriteLine("MQTT application message is published."); - } + Console.WriteLine("MQTT application message is published."); } public static async Task Publish_Multiple_Application_Messages() @@ -54,38 +52,36 @@ public static async Task Publish_Multiple_Application_Messages() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("broker.hivemq.com") - .Build(); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("broker.hivemq.com") + .Build(); - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("samples/temperature/living_room") - .WithPayload("19.5") - .Build(); + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("samples/temperature/living_room") + .WithPayload("19.5") + .Build(); - await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); + await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); - applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("samples/temperature/living_room") - .WithPayload("20.0") - .Build(); + applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("samples/temperature/living_room") + .WithPayload("20.0") + .Build(); - await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); + await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); - applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("samples/temperature/living_room") - .WithPayload("21.0") - .Build(); + applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("samples/temperature/living_room") + .WithPayload("21.0") + .Build(); - await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); + await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); - await mqttClient.DisconnectAsync(); + await mqttClient.DisconnectAsync(); - Console.WriteLine("MQTT application message is published."); - } + Console.WriteLine("MQTT application message is published."); } } \ No newline at end of file diff --git a/Samples/Client/Client_Subscribe_Samples.cs b/Samples/Client/Client_Subscribe_Samples.cs index 96a2a6f26..058d3ff91 100644 --- a/Samples/Client/Client_Subscribe_Samples.cs +++ b/Samples/Client/Client_Subscribe_Samples.cs @@ -26,32 +26,30 @@ public static async Task Handle_Received_Application_Message() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); - // Setup message handling before connecting so that queued messages - // are also handled properly. When there is no event handler attached all - // received messages get lost. - mqttClient.ApplicationMessageReceivedAsync += e => - { - Console.WriteLine("Received application message."); - e.DumpToConsole(); + // Setup message handling before connecting so that queued messages + // are also handled properly. When there is no event handler attached all + // received messages get lost. + mqttClient.ApplicationMessageReceivedAsync += e => + { + Console.WriteLine("Received application message."); + e.DumpToConsole(); - return Task.CompletedTask; - }; + return Task.CompletedTask; + }; - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build(); + var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build(); - await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); + await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); - Console.WriteLine("MQTT client subscribed to topic."); + Console.WriteLine("MQTT client subscribed to topic."); - Console.WriteLine("Press enter to exit."); - Console.ReadLine(); - } + Console.WriteLine("Press enter to exit."); + Console.ReadLine(); } public static async Task Send_Responses() @@ -62,36 +60,34 @@ public static async Task Send_Responses() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) + using var mqttClient = mqttFactory.CreateMqttClient(); + mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args) { - mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args) - { - // Do some work with the message... + // Do some work with the message... - // Now respond to the broker with a reason code other than success. - args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError; - args.ResponseReasonString = "That did not work!"; + // Now respond to the broker with a reason code other than success. + args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError; + args.ResponseReasonString = "That did not work!"; - // User properties require MQTT v5! - args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data")); + // User properties require MQTT v5! + args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data")); - // Now the broker will resend the message again. - return Task.CompletedTask; - }; + // Now the broker will resend the message again. + return Task.CompletedTask; + }; - var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build(); + var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build(); - var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); + var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); - Console.WriteLine("MQTT client subscribed to topic."); + Console.WriteLine("MQTT client subscribed to topic."); - // The response contains additional data sent by the server after subscribing. - response.DumpToConsole(); - } + // The response contains additional data sent by the server after subscribing. + response.DumpToConsole(); } public static async Task Subscribe_Multiple_Topics() @@ -102,27 +98,25 @@ public static async Task Subscribe_Multiple_Topics() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - // Create the subscribe options including several topics with different options. - // It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic. - var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() - .WithTopicTemplate(sampleTemplate.WithParameter("id", "1")) - .WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true) - .WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe) - .Build(); + // Create the subscribe options including several topics with different options. + // It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic. + var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() + .WithTopicTemplate(sampleTemplate.WithParameter("id", "1")) + .WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true) + .WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe) + .Build(); - var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); + var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); - Console.WriteLine("MQTT client subscribed to topics."); + Console.WriteLine("MQTT client subscribed to topics."); - // The response contains additional data sent by the server after subscribing. - response.DumpToConsole(); - } + // The response contains additional data sent by the server after subscribing. + response.DumpToConsole(); } public static async Task Subscribe_Topic() @@ -133,21 +127,19 @@ public static async Task Subscribe_Topic() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build(); + var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build(); - var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); + var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); - Console.WriteLine("MQTT client subscribed to topic."); + Console.WriteLine("MQTT client subscribed to topic."); - // The response contains additional data sent by the server after subscribing. - response.DumpToConsole(); - } + // The response contains additional data sent by the server after subscribing. + response.DumpToConsole(); } static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient) diff --git a/Samples/Diagnostics/Logger_Samples.cs b/Samples/Diagnostics/Logger_Samples.cs index 505ad7489..1e6d9b2d2 100644 --- a/Samples/Diagnostics/Logger_Samples.cs +++ b/Samples/Diagnostics/Logger_Samples.cs @@ -26,17 +26,15 @@ public static async Task Create_Custom_Logger() .WithTcpServer("broker.hivemq.com") .Build(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + using var mqttClient = mqttFactory.CreateMqttClient(); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - Console.WriteLine("MQTT client is connected."); + Console.WriteLine("MQTT client is connected."); - var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() - .Build(); + var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() + .Build(); - await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); - } + await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); } public static async Task Use_Event_Logger() @@ -50,7 +48,7 @@ public static async Task Use_Event_Logger() // The logger ID is optional but can be set do distinguish different logger instances. var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger"); - mqttEventLogger.LogMessagePublished += (sender, args) => + mqttEventLogger.LogMessagePublished += (_, args) => { var output = new StringBuilder(); output.AppendLine($">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}"); @@ -68,17 +66,15 @@ public static async Task Use_Event_Logger() .WithTcpServer("broker.hivemq.com") .Build(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + using var mqttClient = mqttFactory.CreateMqttClient(); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - Console.WriteLine("MQTT client is connected."); + Console.WriteLine("MQTT client is connected."); - var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() - .Build(); + var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() + .Build(); - await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); - } + await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); } sealed class MyLogger : IMqttNetLogger diff --git a/Samples/Diagnostics/PackageInspection_Samples.cs b/Samples/Diagnostics/PackageInspection_Samples.cs index 1ab996a28..7a4d94b36 100644 --- a/Samples/Diagnostics/PackageInspection_Samples.cs +++ b/Samples/Diagnostics/PackageInspection_Samples.cs @@ -20,23 +20,21 @@ public static async Task Inspect_Outgoing_Package() var mqttFactory = new MqttClientFactory(); - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() - .WithTcpServer("broker.hivemq.com") - .Build(); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() + .WithTcpServer("broker.hivemq.com") + .Build(); - mqttClient.InspectPacketAsync += OnInspectPacket; + mqttClient.InspectPacketAsync += OnInspectPacket; - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - Console.WriteLine("MQTT client is connected."); + Console.WriteLine("MQTT client is connected."); - var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() - .Build(); + var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() + .Build(); - await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); - } + await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); } static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) diff --git a/Samples/RpcClient/RpcClient_Samples.cs b/Samples/RpcClient/RpcClient_Samples.cs index b50c91218..5d3c0b237 100644 --- a/Samples/RpcClient/RpcClient_Samples.cs +++ b/Samples/RpcClient/RpcClient_Samples.cs @@ -26,23 +26,21 @@ public static async Task Send_Request() // The RPC client is an addon for the existing client. So we need a regular client // which is wrapped later. - using (var mqttClient = mqttFactory.CreateMqttClient()) - { - var mqttClientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("broker.hivemq.com") - .Build(); - - await mqttClient.ConnectAsync(mqttClientOptions); + using var mqttClient = mqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("broker.hivemq.com") + .Build(); - using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient)) - { - // Access to a fully featured application message is not supported for RPC calls! - // The method will throw an exception when the response was not received in time. - await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); - } + await mqttClient.ConnectAsync(mqttClientOptions); - Console.WriteLine("The RPC call was successful."); + using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient)) + { + // Access to a fully featured application message is not supported for RPC calls! + // The method will throw an exception when the response was not received in time. + await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); } + + Console.WriteLine("The RPC call was successful."); } /* diff --git a/Samples/Server/Server_Diagnostics_Samples.cs b/Samples/Server/Server_Diagnostics_Samples.cs index 07bc8e0b6..3994e9c7b 100644 --- a/Samples/Server/Server_Diagnostics_Samples.cs +++ b/Samples/Server/Server_Diagnostics_Samples.cs @@ -25,28 +25,26 @@ public static async Task Get_Notified_When_Client_Received_Message() var mqttServerFactory = new MqttServerFactory(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) + using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + // Attach the event handler. + mqttServer.ClientAcknowledgedPublishPacketAsync += e => { - // Attach the event handler. - mqttServer.ClientAcknowledgedPublishPacketAsync += e => - { - Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'"); + Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'"); - // It is also possible to read additional data from the client response. This requires casting the response packet. - var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket; - Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}"); + // It is also possible to read additional data from the client response. This requires casting the response packet. + var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket; + Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}"); - var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket; - Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}"); - return CompletedTask.Instance; - }; + var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket; + Console.WriteLine($"QoS 2 reason code: {qos2AcknowledgePacket?.ReasonCode}"); + return CompletedTask.Instance; + }; - await mqttServer.StartAsync(); + await mqttServer.StartAsync(); - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); - await mqttServer.StopAsync(); - } + await mqttServer.StopAsync(); } } \ No newline at end of file diff --git a/Samples/Server/Server_Intercepting_Samples.cs b/Samples/Server/Server_Intercepting_Samples.cs index 31bfa39bd..68c5c8ba0 100644 --- a/Samples/Server/Server_Intercepting_Samples.cs +++ b/Samples/Server/Server_Intercepting_Samples.cs @@ -25,23 +25,21 @@ public static async Task Intercept_Application_Messages() var mqttServerFactory = new MqttServerFactory(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) + using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + mqttServer.InterceptingPublishAsync += args => { - mqttServer.InterceptingPublishAsync += args => - { - // Here we only change the topic of the received application message. - // but also changing the payload etc. is required. Changing the QoS after - // transmitting is not supported and makes no sense at all. - args.ApplicationMessage.Topic += "/manipulated"; - - return CompletedTask.Instance; - }; - - await mqttServer.StartAsync(); - - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); - await mqttServer.StopAsync(); - } + // Here we only change the topic of the received application message. + // but also changing the payload etc. is required. Changing the QoS after + // transmitting is not supported and makes no sense at all. + args.ApplicationMessage.Topic += "/manipulated"; + + return CompletedTask.Instance; + }; + + await mqttServer.StartAsync(); + + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); + await mqttServer.StopAsync(); } } \ No newline at end of file diff --git a/Samples/Server/Server_Retained_Messages_Samples.cs b/Samples/Server/Server_Retained_Messages_Samples.cs index 4564acae9..38159ce33 100644 --- a/Samples/Server/Server_Retained_Messages_Samples.cs +++ b/Samples/Server/Server_Retained_Messages_Samples.cs @@ -29,64 +29,62 @@ public static async Task Persist_Retained_Messages() // Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default! var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build(); - using (var server = mqttServerFactory.CreateMqttServer(mqttServerOptions)) + using var server = mqttServerFactory.CreateMqttServer(mqttServerOptions); + // Make sure that the server will load the retained messages. + server.LoadingRetainedMessageAsync += async eventArgs => { - // Make sure that the server will load the retained messages. - server.LoadingRetainedMessageAsync += async eventArgs => + try { - try - { - var models = await JsonSerializer.DeserializeAsync>(File.OpenRead(storePath)) ?? new List(); - var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList(); - - eventArgs.LoadedRetainedMessages = retainedMessages; - Console.WriteLine("Retained messages loaded."); - } - catch (FileNotFoundException) - { - // Ignore because nothing is stored yet. - Console.WriteLine("No retained messages stored yet."); - } - catch (Exception exception) - { - Console.WriteLine(exception); - } - }; + var models = await JsonSerializer.DeserializeAsync>(File.OpenRead(storePath)) ?? new List(); + var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList(); - // Make sure to persist the changed retained messages. - server.RetainedMessageChangedAsync += async eventArgs => + eventArgs.LoadedRetainedMessages = retainedMessages; + Console.WriteLine("Retained messages loaded."); + } + catch (FileNotFoundException) { - try - { - // This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages. - // The event args also contain the affected retained message (property ChangedRetainedMessage). This can be - // used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list - // of retained messages must be provided in the loaded event. - - var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create); - - var buffer = JsonSerializer.SerializeToUtf8Bytes(models); - await File.WriteAllBytesAsync(storePath, buffer); - Console.WriteLine("Retained messages saved."); - } - catch (Exception exception) - { - Console.WriteLine(exception); - } - }; + // Ignore because nothing is stored yet. + Console.WriteLine("No retained messages stored yet."); + } + catch (Exception exception) + { + Console.WriteLine(exception); + } + }; - // Make sure to clear the retained messages when they are all deleted via API. - server.RetainedMessagesClearedAsync += _ => + // Make sure to persist the changed retained messages. + server.RetainedMessageChangedAsync += async eventArgs => + { + try { - File.Delete(storePath); - return Task.CompletedTask; - }; + // This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages. + // The event args also contain the affected retained message (property ChangedRetainedMessage). This can be + // used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list + // of retained messages must be provided in the loaded event. + + var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create); + + var buffer = JsonSerializer.SerializeToUtf8Bytes(models); + await File.WriteAllBytesAsync(storePath, buffer); + Console.WriteLine("Retained messages saved."); + } + catch (Exception exception) + { + Console.WriteLine(exception); + } + }; + + // Make sure to clear the retained messages when they are all deleted via API. + server.RetainedMessagesClearedAsync += _ => + { + File.Delete(storePath); + return Task.CompletedTask; + }; - await server.StartAsync(); + await server.StartAsync(); - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); - } + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); } sealed class MqttRetainedMessageModel diff --git a/Samples/Server/Server_Simple_Samples.cs b/Samples/Server/Server_Simple_Samples.cs index e7ed4bfec..06eb9e86e 100644 --- a/Samples/Server/Server_Simple_Samples.cs +++ b/Samples/Server/Server_Simple_Samples.cs @@ -22,17 +22,15 @@ public static async Task Force_Disconnecting_Client() * See _Run_Minimal_Server_ for more information. */ - using (var mqttServer = await StartMqttServer()) - { - // Let the client connect. - await Task.Delay(TimeSpan.FromSeconds(5)); + using var mqttServer = await StartMqttServer(); + // Let the client connect. + await Task.Delay(TimeSpan.FromSeconds(5)); - // Now disconnect the client (if connected). - var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient"); - if (affectedClient != null) - { - await affectedClient.DisconnectAsync(); - } + // Now disconnect the client (if connected). + var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient"); + if (affectedClient != null) + { + await affectedClient.DisconnectAsync(); } } @@ -44,18 +42,16 @@ public static async Task Publish_Message_From_Broker() * See _Run_Minimal_Server_ for more information. */ - using (var mqttServer = await StartMqttServer()) - { - // Create a new message using the builder as usual. - var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build(); + using var mqttServer = await StartMqttServer(); + // Create a new message using the builder as usual. + var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build(); - // Now inject the new message at the broker. - await mqttServer.InjectApplicationMessage( - new InjectedMqttApplicationMessage(message) - { - SenderClientId = "SenderClientId" - }); - } + // Now inject the new message at the broker. + await mqttServer.InjectApplicationMessage( + new InjectedMqttApplicationMessage(message) + { + SenderClientId = "SenderClientId" + }); } public static async Task Run_Minimal_Server() @@ -77,16 +73,14 @@ public static async Task Run_Minimal_Server() // .WithDefaultEndpointPort(1234) // .Build(); - using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) - { - await mqttServer.StartAsync(); + using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + await mqttServer.StartAsync(); - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); - // Stop and dispose the MQTT server if it is no longer needed! - await mqttServer.StopAsync(); - } + // Stop and dispose the MQTT server if it is no longer needed! + await mqttServer.StopAsync(); } public static async Task Run_Server_With_Logging() @@ -103,16 +97,14 @@ public static async Task Run_Server_With_Logging() var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) - { - await mqttServer.StartAsync(); + using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + await mqttServer.StartAsync(); - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); - // Stop and dispose the MQTT server if it is no longer needed! - await mqttServer.StopAsync(); - } + // Stop and dispose the MQTT server if it is no longer needed! + await mqttServer.StopAsync(); } public static async Task Validating_Connections() @@ -127,37 +119,35 @@ public static async Task Validating_Connections() var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) + using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + // Setup connection validation before starting the server so that there is + // no change to connect without valid credentials. + mqttServer.ValidatingConnectionAsync += e => { - // Setup connection validation before starting the server so that there is - // no change to connect without valid credentials. - mqttServer.ValidatingConnectionAsync += e => + if (e.ClientId != "ValidClientId") { - if (e.ClientId != "ValidClientId") - { - e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; - } + e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; + } - if (e.UserName != "ValidUser") - { - e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; - } + if (e.UserName != "ValidUser") + { + e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + } - if (e.Password != "SecretPassword") - { - e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; - } + if (e.Password != "SecretPassword") + { + e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + } - return Task.CompletedTask; - }; + return Task.CompletedTask; + }; - await mqttServer.StartAsync(); + await mqttServer.StartAsync(); - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); - await mqttServer.StopAsync(); - } + await mqttServer.StopAsync(); } static async Task StartMqttServer() diff --git a/Samples/Server/Server_TLS_Samples.cs b/Samples/Server/Server_TLS_Samples.cs index 83d0d6010..ff859ea3d 100644 --- a/Samples/Server/Server_TLS_Samples.cs +++ b/Samples/Server/Server_TLS_Samples.cs @@ -31,16 +31,14 @@ public static async Task Run_Server_With_Self_Signed_Certificate() var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build(); - using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) - { - await mqttServer.StartAsync(); + using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + await mqttServer.StartAsync(); - Console.WriteLine("Press Enter to exit."); - Console.ReadLine(); + Console.WriteLine("Press Enter to exit."); + Console.ReadLine(); - // Stop and dispose the MQTT server if it is no longer needed! - await mqttServer.StopAsync(); - } + // Stop and dispose the MQTT server if it is no longer needed! + await mqttServer.StopAsync(); } static X509Certificate2 CreateSelfSignedCertificate(string oid) @@ -50,26 +48,22 @@ static X509Certificate2 CreateSelfSignedCertificate(string oid) sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); sanBuilder.AddDnsName("localhost"); - using (var rsa = RSA.Create()) - { - var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); + using var rsa = RSA.Create(); + var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); - certRequest.CertificateExtensions.Add( - new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); + certRequest.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); - certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false)); + certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false)); - certRequest.CertificateExtensions.Add(sanBuilder.Build()); + certRequest.CertificateExtensions.Add(sanBuilder.Build()); - using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10))) - { - var pfxCertificate = new X509Certificate2( - certificate.Export(X509ContentType.Pfx), - (string)null!, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); + using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)); + var pfxCertificate = new X509Certificate2( + certificate.Export(X509ContentType.Pfx), + (string)null!, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); - return pfxCertificate; - } - } + return pfxCertificate; } } \ No newline at end of file diff --git a/Source/MQTTnet.AspTestApp/Pages/Index.cshtml.cs b/Source/MQTTnet.AspTestApp/Pages/Index.cshtml.cs index 558db0f52..e72939925 100644 --- a/Source/MQTTnet.AspTestApp/Pages/Index.cshtml.cs +++ b/Source/MQTTnet.AspTestApp/Pages/Index.cshtml.cs @@ -4,20 +4,19 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace MQTTnet.AspTestApp.Pages -{ - public class IndexModel : PageModel - { - private readonly ILogger _logger; +namespace MQTTnet.AspTestApp.Pages; - public IndexModel(ILogger logger) - { - _logger = logger; - } +public class IndexModel : PageModel +{ + readonly ILogger _logger; - public void OnGet() - { + public IndexModel(ILogger logger) + { + _logger = logger; + } - } + public void OnGet() + { + _logger.LogDebug("OnGet"); } } \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs index f98983da9..626991819 100644 --- a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -33,10 +33,8 @@ public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, } var adapter = app.ApplicationServices.GetRequiredService(); - using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false)) - { - await adapter.RunWebSocketConnectionAsync(webSocket, context); - } + using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false); + await adapter.RunWebSocketConnectionAsync(webSocket, context); }); return app; diff --git a/Source/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs index 9ea8922ea..8eee0bae0 100644 --- a/Source/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ConnectionBuilderExtensions.cs @@ -4,13 +4,12 @@ using Microsoft.AspNetCore.Connections; -namespace MQTTnet.AspNetCore +namespace MQTTnet.AspNetCore; + +public static class ConnectionBuilderExtensions { - public static class ConnectionBuilderExtensions + public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder) { - public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder) - { - return builder.UseConnectionHandler(); - } + return builder.UseConnectionHandler(); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs b/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs index 8e96a6c28..af8bbcd20 100644 --- a/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs +++ b/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs @@ -6,19 +6,17 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; -namespace MQTTnet.AspNetCore +namespace MQTTnet.AspNetCore; + +public static class EndpointRouterExtensions { - public static class EndpointRouterExtensions + public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern) { - public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern) - { - ArgumentNullException.ThrowIfNull(endpoints); + ArgumentNullException.ThrowIfNull(endpoints); - endpoints.MapConnectionHandler(pattern, options => - { - options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol; - }); - } + endpoints.MapConnectionHandler(pattern, options => + { + options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol; + }); } -} - +} \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/MqttClientConnectionContextFactory.cs index 0ddbbd8f1..fadf90659 100644 --- a/Source/MQTTnet.AspnetCore/MqttClientConnectionContextFactory.cs +++ b/Source/MQTTnet.AspnetCore/MqttClientConnectionContextFactory.cs @@ -7,28 +7,27 @@ using MQTTnet.Diagnostics.Logger; using MQTTnet.Formatter; -namespace MQTTnet.AspNetCore +namespace MQTTnet.AspNetCore; + +public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory { - public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory + public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger) { - public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger) - { - if (options == null) throw new ArgumentNullException(nameof(options)); + if (options == null) throw new ArgumentNullException(nameof(options)); - switch (options.ChannelOptions) + switch (options.ChannelOptions) + { + case MqttClientTcpOptions tcpOptions: { - case MqttClientTcpOptions tcpOptions: - { - var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint); + var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint); - var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)); - return new MqttConnectionContext(formatter, tcpConnection); - } - default: - { - throw new NotSupportedException(); - } + var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)); + return new MqttConnectionContext(formatter, tcpConnection); + } + default: + { + throw new NotSupportedException(); } } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index b4cbc42a8..dfeb2f3f7 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -33,13 +33,11 @@ public override async Task OnConnectedAsync(ConnectionContext connection) } var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax)); - using (var adapter = new MqttConnectionContext(formatter, connection)) + using var adapter = new MqttConnectionContext(formatter, connection); + var clientHandler = ClientHandler; + if (clientHandler != null) { - var clientHandler = ClientHandler; - if (clientHandler != null) - { - await clientHandler(adapter).ConfigureAwait(false); - } + await clientHandler(adapter).ConfigureAwait(false); } } diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 18a382a12..6f28c1e27 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -43,10 +43,8 @@ public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext h var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate); - using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger)) - { - await clientHandler(channelAdapter).ConfigureAwait(false); - } + using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger); + await clientHandler(channelAdapter).ConfigureAwait(false); } } finally diff --git a/Source/MQTTnet.AspnetCore/SocketAwaitable.cs b/Source/MQTTnet.AspnetCore/SocketAwaitable.cs index 2c9607279..17e03db81 100644 --- a/Source/MQTTnet.AspnetCore/SocketAwaitable.cs +++ b/Source/MQTTnet.AspnetCore/SocketAwaitable.cs @@ -12,34 +12,28 @@ namespace MQTTnet.AspNetCore; -public class SocketAwaitable : ICriticalNotifyCompletion +public class SocketAwaitable(PipeScheduler ioScheduler) : ICriticalNotifyCompletion { - static readonly Action _callbackCompleted = () => + static readonly Action CallbackCompleted = () => { }; - readonly PipeScheduler _ioScheduler; int _bytesTransferred; Action _callback; SocketError _error; - public SocketAwaitable(PipeScheduler ioScheduler) - { - _ioScheduler = ioScheduler; - } - - public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); + public bool IsCompleted => ReferenceEquals(_callback, CallbackCompleted); public void Complete(int bytesTransferred, SocketError socketError) { _error = socketError; _bytesTransferred = bytesTransferred; - var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); + var continuation = Interlocked.Exchange(ref _callback, CallbackCompleted); if (continuation != null) { - _ioScheduler.Schedule(state => ((Action)state)(), continuation); + ioScheduler.Schedule(state => ((Action)state)(), continuation); } } @@ -50,7 +44,7 @@ public SocketAwaitable GetAwaiter() public int GetResult() { - Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); + Debug.Assert(ReferenceEquals(_callback, CallbackCompleted)); _callback = null; @@ -64,7 +58,7 @@ public int GetResult() public void OnCompleted(Action continuation) { - if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) + if (ReferenceEquals(_callback, CallbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), CallbackCompleted)) { Task.Run(continuation); } diff --git a/Source/MQTTnet.AspnetCore/SocketConnection.cs b/Source/MQTTnet.AspnetCore/SocketConnection.cs index 2021eccac..ee5f1cb3d 100644 --- a/Source/MQTTnet.AspnetCore/SocketConnection.cs +++ b/Source/MQTTnet.AspnetCore/SocketConnection.cs @@ -18,11 +18,11 @@ namespace MQTTnet.AspNetCore; public sealed class SocketConnection : ConnectionContext { readonly EndPoint _endPoint; + volatile bool _aborted; IDuplexPipe _application; SocketReceiver _receiver; SocketSender _sender; - Socket _socket; public SocketConnection(EndPoint endPoint) @@ -40,10 +40,13 @@ public SocketConnection(Socket socket) } public override string ConnectionId { get; set; } + public override IFeatureCollection Features { get; } public bool IsConnected { get; private set; } + public override IDictionary Items { get; set; } + public override IDuplexPipe Transport { get; set; } public override ValueTask DisposeAsync() @@ -78,7 +81,7 @@ public async Task StartAsync() IsConnected = true; } - Exception ConnectionAborted() + static Exception ConnectionAborted() { return new MqttCommunicationException("Connection Aborted"); } @@ -95,8 +98,7 @@ async Task DoReceive() { error = new MqttCommunicationException(ex); } - catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted || - ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument) + catch (SocketException ex) when (ex.SocketErrorCode is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or SocketError.InvalidArgument) { if (!_aborted) { @@ -123,10 +125,10 @@ async Task DoReceive() { if (_aborted) { - error = error ?? ConnectionAborted(); + error ??= ConnectionAborted(); } - _application.Output.Complete(error); + await _application.Output.CompleteAsync(error); } } diff --git a/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs b/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs index 27a348d4f..4912fcf48 100644 --- a/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs @@ -9,57 +9,54 @@ using BenchmarkDotNet.Jobs; using MQTTnet.Internal; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[MemoryDiagnoser] +public class AsyncLockBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [MemoryDiagnoser] - public class AsyncLockBenchmark : BaseBenchmark + [Benchmark] + public async Task Synchronize_100_Tasks() { - [Benchmark] - public async Task Synchronize_100_Tasks() - { - const int tasksCount = 100; + const int tasksCount = 100; - var tasks = new Task[tasksCount]; - var asyncLock = new AsyncLock(); - var globalI = 0; + var tasks = new Task[tasksCount]; + var asyncLock = new AsyncLock(); + var globalI = 0; - for (var i = 0; i < tasksCount; i++) - { - tasks[i] = Task.Run( - async () => + for (var i = 0; i < tasksCount; i++) + { + tasks[i] = Task.Run( + async () => + { + using (await asyncLock.EnterAsync().ConfigureAwait(false)) { - using (await asyncLock.EnterAsync().ConfigureAwait(false)) - { - var localI = globalI; - await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data. - localI++; - globalI = localI; - } - }); - } + var localI = globalI; + await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data. + localI++; + globalI = localI; + } + }); + } - await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); - if (globalI != tasksCount) - { - throw new Exception($"Code is broken ({globalI})!"); - } - } - - [Benchmark] - public async Task Wait_100_000_Times() + if (globalI != tasksCount) { - var asyncLock = new AsyncLock(); + throw new Exception($"Code is broken ({globalI})!"); + } + } + + [Benchmark] + public async Task Wait_100_000_Times() + { + var asyncLock = new AsyncLock(); - using (var cancellationToken = new CancellationTokenSource()) + using var cancellationToken = new CancellationTokenSource(); + for (var i = 0; i < 100000; i++) + { + using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false)) { - for (var i = 0; i < 100000; i++) - { - using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false)) - { - } - } } } } diff --git a/Source/MQTTnet.Benchmarks/BaseBenchmark.cs b/Source/MQTTnet.Benchmarks/BaseBenchmark.cs index a3a50ce99..ece739b2e 100644 --- a/Source/MQTTnet.Benchmarks/BaseBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/BaseBenchmark.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +public abstract class BaseBenchmark { - public abstract class BaseBenchmark - { - } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index cbc3f7996..7942cee56 100644 --- a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -12,79 +12,78 @@ using MQTTnet.Packets; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[MemoryDiagnoser] +public sealed class ChannelAdapterBenchmark : BaseBenchmark { - [MemoryDiagnoser] - public sealed class ChannelAdapterBenchmark : BaseBenchmark + MqttChannelAdapter _channelAdapter; + int _iterations; + MqttPublishPacket _packet; + MemoryStream _stream; + + [Benchmark] + public void Receive_10000_Messages() { - MqttChannelAdapter _channelAdapter; - int _iterations; - MqttPublishPacket _packet; - MemoryStream _stream; + _stream.Position = 0; - [Benchmark] - public void Receive_10000_Messages() + for (var i = 0; i < 10000; i++) { - _stream.Position = 0; - - for (var i = 0; i < 10000; i++) - { - _channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); - } - - _stream.Position = 0; + _channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); } - [Benchmark] - public void Send_10000_Messages() - { - _stream.Position = 0; + _stream.Position = 0; + } - for (var i = 0; i < 10000; i++) - { - _channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult(); - } + [Benchmark] + public void Send_10000_Messages() + { + _stream.Position = 0; - _stream.Position = 0; + for (var i = 0; i < 10000; i++) + { + _channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult(); } - [GlobalSetup] - public void Setup() + _stream.Position = 0; + } + + [GlobalSetup] + public void Setup() + { + _packet = new MqttPublishPacket { - _packet = new MqttPublishPacket - { - Topic = "A" - }; + Topic = "A" + }; - var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var serializedPacket = Join(serializer.Encode(_packet).Join()); + var serializedPacket = Join(serializer.Encode(_packet).Join()); - _iterations = 10000; + _iterations = 10000; - _stream = new MemoryStream(_iterations * serializedPacket.Length); + _stream = new MemoryStream(_iterations * serializedPacket.Length); - for (var i = 0; i < _iterations; i++) - { - _stream.Write(serializedPacket, 0, serializedPacket.Length); - } + for (var i = 0; i < _iterations; i++) + { + _stream.Write(serializedPacket, 0, serializedPacket.Length); + } - _stream.Position = 0; + _stream.Position = 0; - var channel = new MemoryMqttChannel(_stream); + var channel = new MemoryMqttChannel(_stream); - _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger()); - } + _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger()); + } - static byte[] Join(params ArraySegment[] chunks) + static byte[] Join(params ArraySegment[] chunks) + { + var buffer = new MemoryStream(); + foreach (var chunk in chunks) { - var buffer = new MemoryStream(); - foreach (var chunk in chunks) - { - buffer.Write(chunk.Array, chunk.Offset, chunk.Count); - } - - return buffer.ToArray(); + buffer.Write(chunk.Array, chunk.Offset, chunk.Count); } + + return buffer.ToArray(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs b/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs index 6b8b5e410..53703820a 100644 --- a/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs @@ -2,90 +2,90 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter] +[MemoryDiagnoser] +public class LoggerBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter] - [MemoryDiagnoser] - public class LoggerBenchmark : BaseBenchmark - { - MqttNetNullLogger _nullLogger; - MqttNetSourceLogger _sourceNullLogger; + MqttNetNullLogger _nullLogger; + MqttNetSourceLogger _sourceNullLogger; - MqttNetEventLogger _eventLogger; - MqttNetSourceLogger _sourceEventLogger; + MqttNetEventLogger _eventLogger; + MqttNetSourceLogger _sourceEventLogger; - MqttNetEventLogger _eventLoggerNoListener; - MqttNetSourceLogger _sourceEventLoggerNoListener; + MqttNetEventLogger _eventLoggerNoListener; + MqttNetSourceLogger _sourceEventLoggerNoListener; - bool _useHandler; + bool _useHandler; - [GlobalSetup] - public void Setup() - { - _nullLogger = new MqttNetNullLogger(); - _sourceNullLogger = _nullLogger.WithSource("Source"); + [GlobalSetup] + public void Setup() + { + _nullLogger = new MqttNetNullLogger(); + _sourceNullLogger = _nullLogger.WithSource("Source"); - _eventLogger = new MqttNetEventLogger(); - _eventLogger.LogMessagePublished += OnLogMessagePublished; - _sourceEventLogger = _eventLogger.WithSource("Source"); + _eventLogger = new MqttNetEventLogger(); + _eventLogger.LogMessagePublished += OnLogMessagePublished; + _sourceEventLogger = _eventLogger.WithSource("Source"); - _eventLoggerNoListener = new MqttNetEventLogger(); - _sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source"); - } + _eventLoggerNoListener = new MqttNetEventLogger(); + _sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source"); + } - void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs) + void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs) + { + if (_useHandler) { - if (_useHandler) - { - eventArgs.LogMessage.ToString(); - } + Debug.WriteLine(eventArgs.LogMessage.ToString()); } + } - [Benchmark] - public void Log_10000_Messages_No_Listener() - { - _useHandler = false; + [Benchmark] + public void Log_10000_Messages_No_Listener() + { + _useHandler = false; - for (var i = 0; i < 10000; i++) - { - _sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter"); - } + for (var i = 0; i < 10000; i++) + { + _sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter"); } + } - [Benchmark] - public void Log_10000_Messages_With_To_String() - { - _useHandler = true; + [Benchmark] + public void Log_10000_Messages_With_To_String() + { + _useHandler = true; - for (var i = 0; i < 10000; i++) - { - _sourceEventLogger.Verbose("test log message {0}", "parameter"); - } + for (var i = 0; i < 10000; i++) + { + _sourceEventLogger.Verbose("test log message {0}", "parameter"); } + } - [Benchmark] - public void Log_10000_Messages_Without_To_String() - { - _useHandler = false; + [Benchmark] + public void Log_10000_Messages_Without_To_String() + { + _useHandler = false; - for (var i = 0; i < 10000; i++) - { - _sourceEventLogger.Verbose("test log message {0}", "parameter"); - } + for (var i = 0; i < 10000; i++) + { + _sourceEventLogger.Verbose("test log message {0}", "parameter"); } + } - [Benchmark] - public void Log_10000_Messages_With_NullLogger() + [Benchmark] + public void Log_10000_Messages_With_NullLogger() + { + for (var i = 0; i < 10000; i++) { - for (var i = 0; i < 10000; i++) - { - _sourceNullLogger.Verbose("test log message {0}", "parameter"); - } + _sourceNullLogger.Verbose("test log message {0}", "parameter"); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs index 0733e2bb9..900519c5a 100644 --- a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs @@ -3,38 +3,38 @@ using System; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter, RankColumn] +[MemoryDiagnoser] +public class MemoryCopyBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter, RankColumn] - [MemoryDiagnoser] - public class MemoryCopyBenchmark + const int MaxLength = 1024 * 8; + + byte[] _source; + byte[] _target; + + [Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)] + public int Length { get; set; } + + [GlobalSetup] + public void Setup() + { + _source = new byte[MaxLength]; + _target = new byte[MaxLength]; + } + + [Benchmark(Baseline = true)] + public void Array_Copy() { - const int max_length = 1024 * 8; - private byte[] source; - private byte[] target; - - [Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)] - public int Length { get; set; } - - [GlobalSetup] - public void Setup() - { - source = new byte[max_length]; - target = new byte[max_length]; - } - - [Benchmark(Baseline = true)] - public void Array_Copy() - { - Array.Copy(source, 0, target, 0, Length); - } - - [Benchmark] - public void Memory_Copy() - { - MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length); - } + Array.Copy(_source, 0, _target, 0, Length); + } + [Benchmark] + public void Memory_Copy() + { + Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length); } + } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs index e14eb7b1e..bca655291 100644 --- a/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; @@ -23,21 +22,18 @@ public class MessageDeliveryBenchmark : BaseBenchmark int _messagesExpectedCount; int _messagesReceivedCount; Dictionary _mqttPublisherClientsByPublisherName; - MqttServer _mqttServer; List _mqttSubscriberClients; - Dictionary _publisherByTopic; - List _topicPublishMessages; - - Dictionary> _topicsByPublisher; - [Params(1000, 10000)] public int NumPublishers; + [Params(1000, 10000)] public int _numPublishers; - [Params(5, 10, 20, 50)] public int NumSubscribedTopicsPerSubscriber; + [Params(5, 10, 20, 50)] public int _numSubscribedTopicsPerSubscriber; - [Params(10)] public int NumSubscribers; + [Params(10)] public int _numSubscribers; - [Params(1, 5)] public int NumTopicsPerPublisher; + [Params(1, 5)] public int _numTopicsPerPublisher; + Dictionary _publisherByTopic; + Dictionary> _topicsByPublisher; [GlobalCleanup] public void Cleanup() @@ -64,14 +60,11 @@ public void Cleanup() _mqttServer = null; } - /// - /// Publish messages and wait for messages sent to subscribers - /// [Benchmark] public void DeliverMessages() { // There should be one message received per publish for each subscribed topic - _messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers; + _messagesExpectedCount = _numSubscribedTopicsPerSubscriber * _numSubscribers; // Loop for a while and exchange messages @@ -82,8 +75,6 @@ public void DeliverMessages() // same payload for all messages var payload = new byte[] { 1, 2, 3, 4 }; - var tasks = new List(); - // publish a message for each subscribed topic foreach (var topic in _allSubscribedTopics) { @@ -101,35 +92,23 @@ public void DeliverMessages() } catch { + // Ignore all errors. } _cancellationTokenSource.Dispose(); if (_messagesReceivedCount < _messagesExpectedCount) { - throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount)); + throw new Exception($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}"); } } - [GlobalSetup] public void Setup() { _lockMsgCount = new object(); - Dictionary> singleWildcardTopicsByPublisher; - Dictionary> multiWildcardTopicsByPublisher; - - TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher); - - var topics = _topicsByPublisher.First().Value; - _topicPublishMessages = new List(); - // Prepare messages, same for each publisher - foreach (var topic in topics) - { - var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).Build(); - _topicPublishMessages.Add(message); - } + TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _); // Create server var serverFactory = new MqttServerFactory(); @@ -139,7 +118,7 @@ public void Setup() _mqttServer.StartAsync().GetAwaiter().GetResult(); // Create publisher clients - _mqttPublisherClientsByPublisherName = new Dictionary(); + _mqttPublisherClientsByPublisherName = []; foreach (var pt in _topicsByPublisher) { var publisherName = pt.Key; @@ -151,13 +130,13 @@ public void Setup() // Create subscriber clients _mqttSubscriberClients = new List(); - for (var i = 0; i < NumSubscribers; i++) + for (var i = 0; i < _numSubscribers; i++) { var mqttSubscriberClient = clientFactory.CreateMqttClient(); _mqttSubscriberClients.Add(mqttSubscriberClient); var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build(); - mqttSubscriberClient.ApplicationMessageReceivedAsync += r => + mqttSubscriberClient.ApplicationMessageReceivedAsync += _ => { // count messages and signal cancellation when expected message count is reached lock (_lockMsgCount) @@ -171,12 +150,13 @@ public void Setup() return Task.CompletedTask; }; + mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult(); } var allTopics = new List(); - _publisherByTopic = new Dictionary(); + _publisherByTopic = []; foreach (var t in _topicsByPublisher) { foreach (var topic in t.Value) @@ -189,21 +169,18 @@ public void Setup() // Subscribe to NumSubscribedTopics topics spread across all topics _allSubscribedTopics = new List(); - var totalNumTopics = NumPublishers * NumTopicsPerPublisher; - var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers); - if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics) + var totalNumTopics = _numPublishers * _numTopicsPerPublisher; + var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers); + if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics) { throw new Exception( - string.Format( - "The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}", - totalNumTopics, - topicIndexStep)); + $"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {totalNumTopics}, topic step: {topicIndexStep}"); } var topicIndex = 0; foreach (var mqttSubscriber in _mqttSubscriberClients) { - for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep) + for (var i = 0; i < _numSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep) { var topic = allTopics[topicIndex]; _allSubscribedTopics.Add(topic); diff --git a/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs index b22d365e8..75ef27af7 100644 --- a/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs @@ -9,65 +9,64 @@ using MQTTnet.AspNetCore; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[MemoryDiagnoser] +public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [MemoryDiagnoser] - public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark - { - IWebHost _host; - IMqttClient _mqttClient; - MqttApplicationMessage _message; + IWebHost _host; + IMqttClient _mqttClient; + MqttApplicationMessage _message; - [GlobalSetup] - public void Setup() - { - _host = WebHost.CreateDefaultBuilder() - .UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt())) - .ConfigureServices(services => { - services - .AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint()) - .AddMqttConnectionHandler(); - }) - .Configure(app => { - app.UseMqttServer(s => { + [GlobalSetup] + public void Setup() + { + _host = WebHost.CreateDefaultBuilder() + .UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt())) + .ConfigureServices(services => { + services + .AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint()) + .AddMqttConnectionHandler(); + }) + .Configure(app => { + app.UseMqttServer(_ => { - }); - }) - .Build(); + }); + }) + .Build(); - var factory = new MqttClientFactory(); - _mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory()); + var factory = new MqttClientFactory(); + _mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory()); - _host.StartAsync().GetAwaiter().GetResult(); + _host.StartAsync().GetAwaiter().GetResult(); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost").Build(); - _mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); + _mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); - _message = new MqttApplicationMessageBuilder() - .WithTopic("A") - .Build(); - } + _message = new MqttApplicationMessageBuilder() + .WithTopic("A") + .Build(); + } - [GlobalCleanup] - public void Cleanup() - { - _mqttClient.DisconnectAsync().GetAwaiter().GetResult(); - _mqttClient.Dispose(); + [GlobalCleanup] + public void Cleanup() + { + _mqttClient.DisconnectAsync().GetAwaiter().GetResult(); + _mqttClient.Dispose(); - _host.StopAsync().GetAwaiter().GetResult(); - _host.Dispose(); - } + _host.StopAsync().GetAwaiter().GetResult(); + _host.Dispose(); + } - [Benchmark] - public void Send_10000_Messages() + [Benchmark] + public void Send_10000_Messages() + { + for (var i = 0; i < 10000; i++) { - for (var i = 0; i < 10000; i++) - { - _mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); - } + _mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs index bfa3d209c..8a63e808b 100644 --- a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs @@ -8,36 +8,35 @@ using BenchmarkDotNet.Jobs; using MQTTnet.Formatter; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[MemoryDiagnoser] +public class MqttBufferReaderBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [MemoryDiagnoser] - public class MqttBufferReaderBenchmark + byte[] _buffer; + int _bufferLength; + + [GlobalSetup] + public void GlobalSetup() { - byte[] _buffer; - int _bufferLength; + var writer = new MqttBufferWriter(1024, 1024); + writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib"); - [GlobalSetup] - public void GlobalSetup() - { - var writer = new MqttBufferWriter(1024, 1024); - writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib"); + _buffer = writer.GetBuffer(); + _bufferLength = writer.Length; + } - _buffer = writer.GetBuffer(); - _bufferLength = writer.Length; - } + [Benchmark] + public void Use_Span() + { + var span = _buffer.AsSpan(0, _bufferLength); + Encoding.UTF8.GetString(span); + } - [Benchmark] - public void Use_Span() - { - var span = _buffer.AsSpan(0, _bufferLength); - Encoding.UTF8.GetString(span); - } - - [Benchmark] - public void Use_Encoding() - { - Encoding.UTF8.GetString(_buffer, 0, _bufferLength); - } + [Benchmark] + public void Use_Encoding() + { + Encoding.UTF8.GetString(_buffer, 0, _bufferLength); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs index 0efc7ffac..cd18097ae 100644 --- a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs @@ -8,89 +8,88 @@ using MQTTnet.Formatter; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[MemoryDiagnoser] +public class MqttPacketReaderWriterBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [MemoryDiagnoser] - public class MqttPacketReaderWriterBenchmark : BaseBenchmark + readonly byte[] _demoPayload = new byte[1024]; + + byte[] _readPayload; + + [GlobalCleanup] + public void GlobalCleanup() { - readonly byte[] _demoPayload = new byte[1024]; - - byte[] _readPayload; + } + + [GlobalSetup] + public void GlobalSetup() + { + TestEnvironment.EnableLogger = false; + + var writer = new MqttBufferWriter(4096, 65535); + writer.WriteString("A relative short string."); + writer.WriteBinary(_demoPayload); + writer.WriteByte(0x01); + writer.WriteByte(0x02); + writer.WriteVariableByteInteger(5647382); + writer.WriteString("A relative short string."); + writer.WriteVariableByteInteger(8574489); + writer.WriteBinary(_demoPayload); + writer.WriteByte(2); + writer.WriteByte(0x02); + writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); + writer.WriteBinary(_demoPayload); - [GlobalCleanup] - public void GlobalCleanup() + _readPayload = new ArraySegment(writer.GetBuffer(), 0, writer.Length).ToArray(); + } + + [Benchmark] + public void Read_100_000_Messages() + { + var reader = new MqttBufferReader(); + reader.SetBuffer(_readPayload, 0, _readPayload.Length); + + for (var i = 0; i < 100000; i++) { + reader.Seek(0); + + reader.ReadString(); + reader.ReadBinaryData(); + reader.ReadByte(); + reader.ReadByte(); + reader.ReadVariableByteInteger(); + reader.ReadString(); + reader.ReadVariableByteInteger(); + reader.ReadBinaryData(); + reader.ReadByte(); + reader.ReadByte(); + reader.ReadString(); + reader.ReadBinaryData(); } + } - [GlobalSetup] - public void GlobalSetup() + [Benchmark] + public void Write_100_000_Messages() + { + var writer = new MqttBufferWriter(4096, 65535); + + for (var i = 0; i < 100000; i++) { - TestEnvironment.EnableLogger = false; - - var writer = new MqttBufferWriter(4096, 65535); writer.WriteString("A relative short string."); - writer.WriteBinary(_demoPayload); writer.WriteByte(0x01); writer.WriteByte(0x02); writer.WriteVariableByteInteger(5647382); writer.WriteString("A relative short string."); - writer.WriteVariableByteInteger(8574489); + writer.WriteVariableByteInteger(8574589); writer.WriteBinary(_demoPayload); writer.WriteByte(2); writer.WriteByte(0x02); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteBinary(_demoPayload); - _readPayload = new ArraySegment(writer.GetBuffer(), 0, writer.Length).ToArray(); - } - - [Benchmark] - public void Read_100_000_Messages() - { - var reader = new MqttBufferReader(); - reader.SetBuffer(_readPayload, 0, _readPayload.Length); - - for (var i = 0; i < 100000; i++) - { - reader.Seek(0); - - reader.ReadString(); - reader.ReadBinaryData(); - reader.ReadByte(); - reader.ReadByte(); - reader.ReadVariableByteInteger(); - reader.ReadString(); - reader.ReadVariableByteInteger(); - reader.ReadBinaryData(); - reader.ReadByte(); - reader.ReadByte(); - reader.ReadString(); - reader.ReadBinaryData(); - } - } - - [Benchmark] - public void Write_100_000_Messages() - { - var writer = new MqttBufferWriter(4096, 65535); - - for (var i = 0; i < 100000; i++) - { - writer.WriteString("A relative short string."); - writer.WriteByte(0x01); - writer.WriteByte(0x02); - writer.WriteVariableByteInteger(5647382); - writer.WriteString("A relative short string."); - writer.WriteVariableByteInteger(8574589); - writer.WriteBinary(_demoPayload); - writer.WriteByte(2); - writer.WriteByte(0x02); - writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); - writer.WriteBinary(_demoPayload); - - writer.Reset(0); - } + writer.Reset(0); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 613647471..fce8cf1eb 100644 --- a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Buffers; using System.Reflection; using System.Threading; diff --git a/Source/MQTTnet.Benchmarks/Program.cs b/Source/MQTTnet.Benchmarks/Program.cs index d395269f2..481f87faa 100644 --- a/Source/MQTTnet.Benchmarks/Program.cs +++ b/Source/MQTTnet.Benchmarks/Program.cs @@ -5,138 +5,135 @@ using System; using System.Collections.Generic; using System.Linq; -using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Running; -using MQTTnet.Diagnostics; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +public static class Program { - public static class Program + static int _selectedBenchmarkIndex; + static List _benchmarks; + + public static void Main(string[] arguments) { - static int _selectedBenchmarkIndex; - static List _benchmarks; + _benchmarks = CollectBenchmarks(); + HandleArguments(arguments); - public static void Main(string[] arguments) + while (true) { - _benchmarks = CollectBenchmarks(); - HandleArguments(arguments); - - while (true) - { - RenderMenu(); + RenderMenu(); - var key = Console.ReadKey(true); + var key = Console.ReadKey(true); - if (key.Key == ConsoleKey.DownArrow) - { - _selectedBenchmarkIndex++; - if (_selectedBenchmarkIndex > _benchmarks.Count - 1) - { - _selectedBenchmarkIndex = 0; - } - } - else if (key.Key == ConsoleKey.UpArrow) - { - _selectedBenchmarkIndex--; - if (_selectedBenchmarkIndex < 0) - { - _selectedBenchmarkIndex = _benchmarks.Count - 1; - } - } - else if (key.Key == ConsoleKey.Escape) + if (key.Key == ConsoleKey.DownArrow) + { + _selectedBenchmarkIndex++; + if (_selectedBenchmarkIndex > _benchmarks.Count - 1) { - Environment.Exit(0); + _selectedBenchmarkIndex = 0; } - else if (key.Key == ConsoleKey.Enter) + } + else if (key.Key == ConsoleKey.UpArrow) + { + _selectedBenchmarkIndex--; + if (_selectedBenchmarkIndex < 0) { - break; + _selectedBenchmarkIndex = _benchmarks.Count - 1; } } + else if (key.Key == ConsoleKey.Escape) + { + Environment.Exit(0); + } + else if (key.Key == ConsoleKey.Enter) + { + break; + } + } - BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]); + BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Press any key to exit"); - Console.ReadLine(); - } + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + } - static List CollectBenchmarks() - { - var benchmarks = new List(); + static List CollectBenchmarks() + { + var benchmarks = new List(); - var types = typeof(Program).Assembly.GetExportedTypes(); - foreach (var type in types) + var types = typeof(Program).Assembly.GetExportedTypes(); + foreach (var type in types) + { + if (typeof(BaseBenchmark).IsAssignableFrom(type)) { - if (typeof(BaseBenchmark).IsAssignableFrom(type)) + if (type != typeof(BaseBenchmark)) { - if (type != typeof(BaseBenchmark)) - { - benchmarks.Add(type); - } + benchmarks.Add(type); } } - - return benchmarks.OrderBy(b => b.Name).ToList(); } - static void HandleArguments(string[] arguments) - { - if (arguments.Length == 0) - { - return; - } + return benchmarks.OrderBy(b => b.Name).ToList(); + } - // Allow for preselection to avoid developer frustration. + static void HandleArguments(string[] arguments) + { + if (arguments.Length == 0) + { + return; + } - if (int.TryParse(arguments[0], out var benchmarkIndex)) - { - _selectedBenchmarkIndex = benchmarkIndex; - return; - } + // Allow for preselection to avoid developer frustration. - _selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0])); + if (int.TryParse(arguments[0], out var benchmarkIndex)) + { + _selectedBenchmarkIndex = benchmarkIndex; + return; + } - if (_selectedBenchmarkIndex < 0) - { - _selectedBenchmarkIndex = 0; - } + _selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0])); - if (_selectedBenchmarkIndex > _benchmarks.Count - 1) - { - _selectedBenchmarkIndex = _benchmarks.Count - 1; - } + if (_selectedBenchmarkIndex < 0) + { + _selectedBenchmarkIndex = 0; } - static void RenderMenu() + if (_selectedBenchmarkIndex > _benchmarks.Count - 1) { - Console.Clear(); - - Console.WriteLine($"MQTTnet - Benchmarks"); - Console.WriteLine("-----------------------------------------------"); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Press arrow keys for benchmark selection"); - Console.WriteLine("Press Enter for benchmark execution"); - Console.WriteLine("Press Esc for exit"); - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(); - - for (var index = 0; index < _benchmarks.Count; index++) - { - var benchmark = _benchmarks[index]; + _selectedBenchmarkIndex = _benchmarks.Count - 1; + } + } - if (_selectedBenchmarkIndex == index) - { - Console.Write("-> "); - } - else - { - Console.Write(" "); - } + static void RenderMenu() + { + Console.Clear(); + + Console.WriteLine($"MQTTnet - Benchmarks"); + Console.WriteLine("-----------------------------------------------"); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("Press arrow keys for benchmark selection"); + Console.WriteLine("Press Enter for benchmark execution"); + Console.WriteLine("Press Esc for exit"); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(); + + for (var index = 0; index < _benchmarks.Count; index++) + { + var benchmark = _benchmarks[index]; - Console.WriteLine(benchmark.Name); + if (_selectedBenchmarkIndex == index) + { + Console.Write("-> "); + } + else + { + Console.Write(" "); } - Console.WriteLine(); + Console.WriteLine(benchmark.Name); } + + Console.WriteLine(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs index 5f2242461..1779a57d9 100644 --- a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs @@ -1,240 +1,86 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -using MQTTnet.Adapter; using MQTTnet.AspNetCore; using MQTTnet.Exceptions; using MQTTnet.Formatter; -using MQTTnet.Packets; -using System; using System.Buffers; using System.IO; using System.IO.Pipelines; -using System.Linq; using System.Threading.Tasks; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter, RankColumn] +[MemoryDiagnoser] +public class ReaderExtensionsBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter, RankColumn] - [MemoryDiagnoser] - public class ReaderExtensionsBenchmark - { - MqttPacketFormatterAdapter mqttPacketFormatter; - MemoryStream stream; + MqttPacketFormatterAdapter _mqttPacketFormatter; + MemoryStream _stream; - [GlobalSetup] - public void GlobalSetup() - { - mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var mqttMessage = new MqttApplicationMessageBuilder() + [GlobalSetup] + public void GlobalSetup() + { + _mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); + var mqttMessage = new MqttApplicationMessageBuilder() .WithTopic("topic") .WithPayload(new byte[10 * 1024]) .Build(); - var packet = MqttPublishPacketFactory.Create(mqttMessage); + var packet = MqttPublishPacketFactory.Create(mqttMessage); - var buffer = mqttPacketFormatter.Encode(packet); - stream = new MemoryStream(); - stream.Write(buffer.Packet); - stream.Write(buffer.Payload.ToArray()); - mqttPacketFormatter.Cleanup(); - } + var buffer = _mqttPacketFormatter.Encode(packet); + _stream = new MemoryStream(); + _stream.Write(buffer.Packet); + _stream.Write(buffer.Payload.ToArray()); + _mqttPacketFormatter.Cleanup(); + } - [Benchmark(Baseline = true)] - public async Task Before() - { - stream.Position = 0; - var input = PipeReader.Create(stream); + [Benchmark] + public async Task After() + { + _stream.Position = 0; + var input = PipeReader.Create(_stream); - while (true) + while (true) + { + ReadResult readResult; + var readTask = input.ReadAsync(); + if (readTask.IsCompleted) { - ReadResult readResult; - var readTask = input.ReadAsync(); - if (readTask.IsCompleted) - { - readResult = readTask.Result; - } - else - { - readResult = await readTask.ConfigureAwait(false); - } - - var buffer = readResult.Buffer; - - var consumed = buffer.Start; - var observed = buffer.Start; - - try - { - if (!buffer.IsEmpty) - { - if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received)) - { - break; - } - } - else if (readResult.IsCompleted) - { - throw new MqttCommunicationException("Connection Aborted"); - } - } - finally - { - // The buffer was sliced up to where it was consumed, so we can just advance to the start. - // We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data - // before yielding the read again. - input.AdvanceTo(consumed, observed); - } + readResult = readTask.Result; } - } - - [Benchmark] - public async Task After() - { - stream.Position = 0; - var input = PipeReader.Create(stream); - - while (true) + else { - ReadResult readResult; - var readTask = input.ReadAsync(); - if (readTask.IsCompleted) - { - readResult = readTask.Result; - } - else - { - readResult = await readTask.ConfigureAwait(false); - } + readResult = await readTask.ConfigureAwait(false); + } - var buffer = readResult.Buffer; + var buffer = readResult.Buffer; - var consumed = buffer.Start; - var observed = buffer.Start; + var consumed = buffer.Start; + var observed = buffer.Start; - try + try + { + if (!buffer.IsEmpty) { - if (!buffer.IsEmpty) + if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _)) { - if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received)) - { - break; - } - } - else if (readResult.IsCompleted) - { - throw new MqttCommunicationException("Connection Aborted"); + break; } } - finally + else if (readResult.IsCompleted) { - // The buffer was sliced up to where it was consumed, so we can just advance to the start. - // We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data - // before yielding the read again. - input.AdvanceTo(consumed, observed); + throw new MqttCommunicationException("Connection Aborted"); } } - } - - public static class ReaderExtensions_Before - { - public static bool TryDecode(MqttPacketFormatterAdapter formatter, - in ReadOnlySequence input, - out MqttPacket packet, - out SequencePosition consumed, - out SequencePosition observed, - out int bytesRead) + finally { - ArgumentNullException.ThrowIfNull(formatter); - - packet = null; - consumed = input.Start; - observed = input.End; - bytesRead = 0; - var copy = input; - - if (copy.Length < 2) - { - return false; - } - - var fixedHeader = copy.First.Span[0]; - if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength)) - { - return false; - } - - if (copy.Length < bodyLength) - { - return false; - } - - var bodySlice = copy.Slice(0, bodyLength); - var buffer = GetMemory(bodySlice).ToArray(); - - var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment(buffer, 0, buffer.Length), buffer.Length + 2); - - if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown) - { - formatter.DetectProtocolVersion(receivedMqttPacket); - } - - packet = formatter.Decode(receivedMqttPacket); - consumed = bodySlice.End; - observed = bodySlice.End; - bytesRead = headerLength + bodyLength; - return true; - } - - static ReadOnlyMemory GetMemory(in ReadOnlySequence input) - { - if (input.IsSingleSegment) - { - return input.First; - } - - // Should be rare - return input.ToArray(); - } - - static bool TryReadBodyLength(ref ReadOnlySequence input, out int headerLength, out int bodyLength) - { - // Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html. - var multiplier = 1; - var value = 0; - byte encodedByte; - var index = 1; - headerLength = 0; - bodyLength = 0; - - var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span; - - do - { - if (index == temp.Length) - { - return false; - } - - encodedByte = temp[index]; - index++; - - value += (byte)(encodedByte & 127) * multiplier; - if (multiplier > 128 * 128 * 128) - { - throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())})."); - } - - multiplier *= 128; - } while ((encodedByte & 128) != 0); - - input = input.Slice(index); - - headerLength = index; - bodyLength = value; - return true; + // The buffer was sliced up to where it was consumed, so we can just advance to the start. + // We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data + // before yielding the read again. + input.AdvanceTo(consumed, observed); } } - } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs index e3358fb91..8f638e2e5 100644 --- a/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs @@ -3,28 +3,27 @@ using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Server; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter, RankColumn] +[MemoryDiagnoser] +public class RoundtripProcessingBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter, RankColumn] - [MemoryDiagnoser] - public class RoundtripProcessingBenchmark : BaseBenchmark + [GlobalSetup] + public void GlobalSetup() + { + TestEnvironment.EnableLogger = false; + } + + [GlobalCleanup] + public void GlobalCleanup() + { + } + + [Benchmark] + public void Handle_100_000_Messages_In_Receiving_Client() { - [GlobalSetup] - public void GlobalSetup() - { - TestEnvironment.EnableLogger = false; - } - - [GlobalCleanup] - public void GlobalCleanup() - { - } - - [Benchmark] - public void Handle_100_000_Messages_In_Receiving_Client() - { - new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult(); - } + new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs index b31782e66..82f145538 100644 --- a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs @@ -7,63 +7,61 @@ using System.IO.Pipelines; using System.Threading.Tasks; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter, RankColumn] +[MemoryDiagnoser] +public class SendPacketAsyncBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter, RankColumn] - [MemoryDiagnoser] - public class SendPacketAsyncBenchmark : BaseBenchmark + MemoryStream _stream; + MqttPacketBuffer _buffer; + + [GlobalSetup] + public void GlobalSetup() { - MemoryStream stream; - MqttPacketBuffer buffer; + _stream = new MemoryStream(1024); + var packet = new ArraySegment(new byte[10]); + _buffer = new MqttPacketBuffer(packet); + } - [GlobalSetup] - public void GlobalSetup() - { - stream = new MemoryStream(1024); - var packet = new ArraySegment(new byte[10]); - buffer = new MqttPacketBuffer(packet); - } + [Benchmark(Baseline = true)] + public async ValueTask Before() + { + _stream.Position = 0; + var output = PipeWriter.Create(_stream); - [Benchmark(Baseline = true)] - public async ValueTask Before() - { - stream.Position = 0; - var output = PipeWriter.Create(stream); + WritePacketBuffer(output, _buffer); + await output.FlushAsync(); + } - WritePacketBuffer(output, buffer); - await output.FlushAsync(); - } + [Benchmark] + public async ValueTask After() + { + _stream.Position = 0; + var output = PipeWriter.Create(_stream); - [Benchmark] - public async ValueTask After() + if (_buffer.Payload.Length == 0) { - stream.Position = 0; - var output = PipeWriter.Create(stream); - - if (buffer.Payload.Length == 0) - { - await output.WriteAsync(buffer.Packet).ConfigureAwait(false); - } - else - { - WritePacketBuffer(output, buffer); - await output.FlushAsync().ConfigureAwait(false); - } + await output.WriteAsync(_buffer.Packet).ConfigureAwait(false); } - - - static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) + else { - // copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter - // MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync + WritePacketBuffer(output, _buffer); + await output.FlushAsync().ConfigureAwait(false); + } + } + + static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) + { + // copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter + // MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync - var span = output.GetSpan(buffer.Length); + var span = output.GetSpan(buffer.Length); - buffer.Packet.AsSpan().CopyTo(span); - buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count)); + buffer.Packet.AsSpan().CopyTo(span); + buffer.Payload.CopyTo(span[buffer.Packet.Count..]); - output.Advance(buffer.Length); - } + output.Advance(buffer.Length); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs index 48117232c..213cf19e5 100644 --- a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -17,103 +17,102 @@ using System.Buffers; using System.Net; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter] +[MemoryDiagnoser] +public class SerializerBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter] - [MemoryDiagnoser] - public class SerializerBenchmark : BaseBenchmark - { - MqttPacket _packet; - ArraySegment _serializedPacket; - IMqttPacketFormatter _serializer; - MqttBufferWriter _bufferWriter; + MqttPacket _packet; + ArraySegment _serializedPacket; + IMqttPacketFormatter _serializer; + MqttBufferWriter _bufferWriter; - [GlobalSetup] - public void GlobalSetup() + [GlobalSetup] + public void GlobalSetup() + { + _packet = new MqttPublishPacket { - _packet = new MqttPublishPacket - { - Topic = "A" - }; - - _bufferWriter = new MqttBufferWriter(4096, 65535); - _serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311); - _serializedPacket = _serializer.Encode(_packet).Join(); - } + Topic = "A" + }; - [Benchmark] - public void Serialize_10000_Messages() + _bufferWriter = new MqttBufferWriter(4096, 65535); + _serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311); + _serializedPacket = _serializer.Encode(_packet).Join(); + } + + [Benchmark] + public void Serialize_10000_Messages() + { + for (var i = 0; i < 10000; i++) { - for (var i = 0; i < 10000; i++) - { - _serializer.Encode(_packet); - _bufferWriter.Cleanup(); - } + _serializer.Encode(_packet); + _bufferWriter.Cleanup(); } + } - [Benchmark] - public void Deserialize_10000_Messages() - { - var channel = new BenchmarkMqttChannel(_serializedPacket); - var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); + [Benchmark] + public void Deserialize_10000_Messages() + { + var channel = new BenchmarkMqttChannel(_serializedPacket); + var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); - for (var i = 0; i < 10000; i++) - { - channel.Reset(); + for (var i = 0; i < 10000; i++) + { + channel.Reset(); - var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); - } + reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); } + } - class BenchmarkMqttChannel : IMqttChannel - { - readonly ArraySegment _buffer; - int _position; + class BenchmarkMqttChannel : IMqttChannel + { + readonly ArraySegment _buffer; + int _position; - public BenchmarkMqttChannel(ArraySegment buffer) - { - _buffer = buffer; - _position = _buffer.Offset; - } + public BenchmarkMqttChannel(ArraySegment buffer) + { + _buffer = buffer; + _position = _buffer.Offset; + } - public EndPoint RemoteEndPoint { get; set; } + public EndPoint RemoteEndPoint { get; set; } - public bool IsSecureConnection { get; } = false; + public bool IsSecureConnection { get; } = false; - public X509Certificate2 ClientCertificate { get; } + public X509Certificate2 ClientCertificate { get; set; } - public void Reset() - { - _position = _buffer.Offset; - } + public void Reset() + { + _position = _buffer.Offset; + } - public Task ConnectAsync(CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } + public Task ConnectAsync(CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } - public Task DisconnectAsync(CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } + public Task DisconnectAsync(CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } - public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - Array.Copy(_buffer.Array, _position, buffer, offset, count); - _position += count; + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + Array.Copy(_buffer!.Array!, _position, buffer, offset, count); + _position += count; - return Task.FromResult(count); - } + return Task.FromResult(count); + } - public Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } + public Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } - public void Dispose() - { - } + public void Dispose() + { } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs index fbac6dc02..9278a44a3 100644 --- a/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs @@ -7,34 +7,33 @@ using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Server; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter, RankColumn] +[MemoryDiagnoser] +public class ServerProcessingBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter, RankColumn] - [MemoryDiagnoser] - public class ServerProcessingBenchmark : BaseBenchmark + [GlobalSetup] + public void GlobalSetup() + { + TestEnvironment.EnableLogger = false; + } + + [GlobalCleanup] + public void GlobalCleanup() + { + } + + [Benchmark] + public void Handle_100_000_Messages_In_Server_MqttClient() + { + new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult(); + } + + //[Benchmark] + public void Handle_100_000_Messages_In_Server_LowLevelMqttClient() { - [GlobalSetup] - public void GlobalSetup() - { - TestEnvironment.EnableLogger = false; - } - - [GlobalCleanup] - public void GlobalCleanup() - { - } - - [Benchmark] - public void Handle_100_000_Messages_In_Server_MqttClient() - { - new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult(); - } - - //[Benchmark] - public void Handle_100_000_Messages_In_Server_LowLevelMqttClient() - { - new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult(); - } + new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs b/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs index 8c56ee62f..7e0d37759 100644 --- a/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs @@ -30,7 +30,7 @@ public void Cleanup() [GlobalSetup] public void Setup() { - TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); + TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _); _topics = topicsByPublisher.Values.First(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); diff --git a/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs b/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs index 7692f78b3..19a080cc1 100644 --- a/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs @@ -11,70 +11,69 @@ using BenchmarkDotNet.Jobs; using MQTTnet.AspNetCore; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[MemoryDiagnoser] +public class TcpPipesBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [MemoryDiagnoser] - public class TcpPipesBenchmark : BaseBenchmark + IDuplexPipe _client; + IDuplexPipe _server; + + [GlobalSetup] + public void Setup() { - IDuplexPipe _client; - IDuplexPipe _server; + var server = new TcpListener(IPAddress.Any, 1883); - [GlobalSetup] - public void Setup() - { - var server = new TcpListener(IPAddress.Any, 1883); + server.Start(1); - server.Start(1); + var task = Task.Run(() => server.AcceptSocket()); - var task = Task.Run(() => server.AcceptSocket()); + var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883)); - var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883)); + clientConnection.StartAsync().GetAwaiter().GetResult(); + _client = clientConnection.Transport; - clientConnection.StartAsync().GetAwaiter().GetResult(); - _client = clientConnection.Transport; + var serverConnection = new SocketConnection(task.GetAwaiter().GetResult()); + serverConnection.StartAsync().GetAwaiter().GetResult(); + _server = serverConnection.Transport; + } - var serverConnection = new SocketConnection(task.GetAwaiter().GetResult()); - serverConnection.StartAsync().GetAwaiter().GetResult(); - _server = serverConnection.Transport; - } + [Benchmark] + public async Task Send_10000_Chunks_Pipe() + { + var size = 5; + var iterations = 10000; - [Benchmark] - public async Task Send_10000_Chunks_Pipe() - { - var size = 5; - var iterations = 10000; + await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); + } - await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); - } + async Task ReadAsync(int iterations, int size) + { + await Task.Yield(); - async Task ReadAsync(int iterations, int size) + var expected = iterations * size; + long read = 0; + var input = _client.Input; + + while (read < expected) { - await Task.Yield(); - - var expected = iterations * size; - long read = 0; - var input = _client.Input; - - while (read < expected) - { - var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false); - input.AdvanceTo(readresult.Buffer.End); - read += readresult.Buffer.Length; - } + var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false); + input.AdvanceTo(readresult.Buffer.End); + read += readresult.Buffer.Length; } + } - async Task WriteAsync(int iterations, int size) - { - await Task.Yield(); + async Task WriteAsync(int iterations, int size) + { + await Task.Yield(); - var output = _server.Output; + var output = _server.Output; - for (var i = 0; i < iterations; i++) - { - await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false); - } + for (var i = 0; i < iterations; i++) + { + await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs b/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs index f78cb81c3..d5e18ba93 100644 --- a/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs @@ -2,269 +2,32 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -namespace MQTTnet.Benchmarks -{ - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter] - [MemoryDiagnoser] - public class TopicFilterComparerBenchmark : BaseBenchmark - { - static readonly char[] TopicLevelSeparator = { '/' }; +namespace MQTTnet.Benchmarks; - readonly string _longTopic = - "AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"; - - [Benchmark] - public void MqttTopicFilterComparer_10000_LoopMethod() - { - for (var i = 0; i < 100000; i++) - { - MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#"); - MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking"); - MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); - MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+"); - MqttTopicFilterComparer.Compare("/finance", "+/+"); - MqttTopicFilterComparer.Compare("/finance", "/+"); - MqttTopicFilterComparer.Compare("/finance", "+"); - MqttTopicFilterComparer.Compare(_longTopic, _longTopic); - } - } - - [Benchmark] - public void MqttTopicFilterComparer_10000_LoopMethod_Without_Pointer() - { - for (var i = 0; i < 100000; i++) - { - MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/#"); - MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/ranking", "sport/#/ranking"); - MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); - MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/tennis/+"); - MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+/+"); - MqttTopicFilterComparerWithoutPointer.Compare("/finance", "/+"); - MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+"); - MqttTopicFilterComparerWithoutPointer.Compare(_longTopic, _longTopic); - } - } - - [Benchmark] - public void MqttTopicFilterComparer_10000_StringSplitMethod() - { - for (var i = 0; i < 100000; i++) - { - LegacyMethodByStringSplit("sport/tennis/player1", "sport/#"); - LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking"); - LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); - LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+"); - LegacyMethodByStringSplit("/finance", "+/+"); - LegacyMethodByStringSplit("/finance", "/+"); - LegacyMethodByStringSplit("/finance", "+"); - MqttTopicFilterComparer.Compare(_longTopic, _longTopic); - } - } - - [GlobalSetup] - public void Setup() - { - } - - static bool LegacyMethodByStringSplit(string topic, string filter) - { - ArgumentNullException.ThrowIfNull(topic); - ArgumentNullException.ThrowIfNull(filter); - - if (string.Equals(topic, filter, StringComparison.Ordinal)) - { - return true; - } - - var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None); - var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None); - - // # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2]. - for (var i = 0; i < fragmentsFilter.Length; i++) - { - if (fragmentsFilter[i] == "+") - { - continue; - } - - if (fragmentsFilter[i] == "#") - { - return true; - } - - if (i >= fragmentsTopic.Length) - { - return false; - } - - if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal)) - { - return false; - } - } - - return fragmentsTopic.Length == fragmentsFilter.Length; - } +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter] +[MemoryDiagnoser] +public class TopicFilterComparerBenchmark : BaseBenchmark +{ + readonly string _longTopic = + "AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"; - public static class MqttTopicFilterComparerWithoutPointer + [Benchmark] + public void MqttTopicFilterComparer_10000_LoopMethod() + { + for (var i = 0; i < 100000; i++) { - public const char LevelSeparator = '/'; - public const char MultiLevelWildcard = '#'; - public const char SingleLevelWildcard = '+'; - public const char ReservedTopicPrefix = '$'; - - public static MqttTopicFilterCompareResult Compare(string topic, string filter) - { - if (string.IsNullOrEmpty(topic)) - { - return MqttTopicFilterCompareResult.TopicInvalid; - } - - if (string.IsNullOrEmpty(filter)) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - var filterOffset = 0; - var filterLength = filter.Length; - - var topicOffset = 0; - var topicLength = topic.Length; - - var topicPointer = topic; - var filterPointer = filter; - - var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard; - var isReservedTopic = topicPointer[0] == ReservedTopicPrefix; - - if (isReservedTopic && filterLength == 1 && isMultiLevelFilter) - { - // It is not allowed to receive i.e. '$foo/bar' with filter '#'. - return MqttTopicFilterCompareResult.NoMatch; - } - - if (isReservedTopic && filterPointer[0] == SingleLevelWildcard) - { - // It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'. - return MqttTopicFilterCompareResult.NoMatch; - } - - if (filterLength == 1 && isMultiLevelFilter) - { - // Filter '#' matches basically everything. - return MqttTopicFilterCompareResult.IsMatch; - } - - // Go through the filter char by char. - while (filterOffset < filterLength && topicOffset < topicLength) - { - // Check if the current char is a multi level wildcard. The char is only allowed - // at the very las position. - if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - if (filterPointer[filterOffset] == topicPointer[topicOffset]) - { - if (topicOffset == topicLength - 1) - { - // Check for e.g. "foo" matching "foo/#" - if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter) - { - return MqttTopicFilterCompareResult.IsMatch; - } - - // Check for e.g. "foo/" matching "foo/#" - if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter) - { - return MqttTopicFilterCompareResult.IsMatch; - } - } - - filterOffset++; - topicOffset++; - - // Check if the end was reached and i.e. "foo/bar" matches "foo/bar" - if (filterOffset == filterLength && topicOffset == topicLength) - { - return MqttTopicFilterCompareResult.IsMatch; - } - - var endOfTopic = topicOffset == topicLength; - - if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard) - { - if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - return MqttTopicFilterCompareResult.IsMatch; - } - } - else - { - if (filterPointer[filterOffset] == SingleLevelWildcard) - { - // Check for invalid "+foo" or "a/+foo" subscription - if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - // Check for bad "foo+" or "foo+/a" subscription - if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - filterOffset++; - while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator) - { - topicOffset++; - } - - if (topicOffset == topicLength && filterOffset == filterLength) - { - return MqttTopicFilterCompareResult.IsMatch; - } - } - else if (filterPointer[filterOffset] == MultiLevelWildcard) - { - if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - if (filterOffset + 1 != filterLength) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - return MqttTopicFilterCompareResult.IsMatch; - } - else - { - // Check for e.g. "foo/bar" matching "foo/+/#". - if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard && - filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter) - { - return MqttTopicFilterCompareResult.IsMatch; - } - - return MqttTopicFilterCompareResult.NoMatch; - } - } - } - - return MqttTopicFilterCompareResult.NoMatch; - } + MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#"); + MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking"); + MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); + MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+"); + MqttTopicFilterComparer.Compare("/finance", "+/+"); + MqttTopicFilterComparer.Compare("/finance", "/+"); + MqttTopicFilterComparer.Compare("/finance", "+"); + MqttTopicFilterComparer.Compare(_longTopic, _longTopic); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/TopicGenerator.cs b/Source/MQTTnet.Benchmarks/TopicGenerator.cs index d4f9d8e76..1b2327ee5 100644 --- a/Source/MQTTnet.Benchmarks/TopicGenerator.cs +++ b/Source/MQTTnet.Benchmarks/TopicGenerator.cs @@ -1,84 +1,83 @@ using System; using System.Collections.Generic; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +public class TopicGenerator { - public class TopicGenerator + public static void Generate( + int numPublishers, + int numTopicsPerPublisher, + out Dictionary> topicsByPublisher, + out Dictionary> singleWildcardTopicsByPublisher, + out Dictionary> multiWildcardTopicsByPublisher) { - public static void Generate( - int numPublishers, - int numTopicsPerPublisher, - out Dictionary> topicsByPublisher, - out Dictionary> singleWildcardTopicsByPublisher, - out Dictionary> multiWildcardTopicsByPublisher) - { - topicsByPublisher = new Dictionary>(); - singleWildcardTopicsByPublisher = new Dictionary>(); - multiWildcardTopicsByPublisher = new Dictionary>(); + topicsByPublisher = new Dictionary>(); + singleWildcardTopicsByPublisher = new Dictionary>(); + multiWildcardTopicsByPublisher = new Dictionary>(); - // Find some reasonable distribution across three topic levels - var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0); - if (topicsPerLevel <= 0) - { - topicsPerLevel = 1; - } + // Find some reasonable distribution across three topic levels + var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0); + if (topicsPerLevel <= 0) + { + topicsPerLevel = 1; + } - var numLevel1Topics = topicsPerLevel; - var numLevel2Topics = topicsPerLevel; + var numLevel1Topics = topicsPerLevel; + var numLevel2Topics = topicsPerLevel; - var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); - if (maxNumLevel3Topics <= 0) - { - maxNumLevel3Topics = 1; - } + var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); + if (maxNumLevel3Topics <= 0) + { + maxNumLevel3Topics = 1; + } - for (var p = 0; p < numPublishers; ++p) + for (var p = 0; p < numPublishers; ++p) + { + var publisherTopicCount = 0; + var publisherName = "pub" + p; + for (var l1 = 0; l1 < numLevel1Topics; ++l1) { - var publisherTopicCount = 0; - var publisherName = "pub" + p; - for (var l1 = 0; l1 < numLevel1Topics; ++l1) + for (var l2 = 0; l2 < numLevel2Topics; ++l2) { - for (var l2 = 0; l2 < numLevel2Topics; ++l2) + for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) { - for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) + if (publisherTopicCount >= numTopicsPerPublisher) { - if (publisherTopicCount >= numTopicsPerPublisher) - { - break; - } - - var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}"; - AddPublisherTopic(publisherName, topic, topicsByPublisher); + break; + } - if (l2 == 0) - { - var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}"; - AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); - } + var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}"; + AddPublisherTopic(publisherName, topic, topicsByPublisher); - if (l1 == 0 && l3 == 0) - { - var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+"; - AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); - } + if (l2 == 0) + { + var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}"; + AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); + } - ++publisherTopicCount; + if (l1 == 0 && l3 == 0) + { + var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+"; + AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); } + + ++publisherTopicCount; } } } } + } - static void AddPublisherTopic(string publisherName, string topic, Dictionary> topicsByPublisher) + static void AddPublisherTopic(string publisherName, string topic, Dictionary> topicsByPublisher) + { + List topicList; + if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) { - List topicList; - if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) - { - topicList = new List(); - topicsByPublisher.Add(publisherName, topicList); - } - - topicList.Add(topic); + topicList = new List(); + topicsByPublisher.Add(publisherName, topicList); } + + topicList.Add(topic); } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs b/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs index 41f3adcdb..8e1ac6756 100644 --- a/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs @@ -14,15 +14,15 @@ public class UnsubscribeBenchmark : BaseBenchmark { const int NumPublishers = 1; const int NumTopicsPerPublisher = 10000; + IMqttClient _mqttClient; MqttServer _mqttServer; - List _topics; [GlobalSetup] public void Setup() { - TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); + TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _); _topics = topicsByPublisher.Values.First(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); diff --git a/Source/MQTTnet.Extensions.Rpc/DefaultMqttRpcClientTopicGenerationStrategy.cs b/Source/MQTTnet.Extensions.Rpc/DefaultMqttRpcClientTopicGenerationStrategy.cs index b21affed2..13d789582 100644 --- a/Source/MQTTnet.Extensions.Rpc/DefaultMqttRpcClientTopicGenerationStrategy.cs +++ b/Source/MQTTnet.Extensions.Rpc/DefaultMqttRpcClientTopicGenerationStrategy.cs @@ -4,27 +4,26 @@ using System; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy { - public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy + public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) { - public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) - { - ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(context); - if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#")) - { - throw new ArgumentException("The method name cannot contain /, + or #."); - } + if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#")) + { + throw new ArgumentException("The method name cannot contain /, + or #."); + } - var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}"; - var responseTopic = requestTopic + "/response"; + var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}"; + var responseTopic = requestTopic + "/response"; - return new MqttRpcTopicPair - { - RequestTopic = requestTopic, - ResponseTopic = responseTopic - }; - } + return new MqttRpcTopicPair + { + RequestTopic = requestTopic, + ResponseTopic = responseTopic + }; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/IMqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/IMqttRpcClient.cs index ccece444a..848460715 100644 --- a/Source/MQTTnet.Extensions.Rpc/IMqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/IMqttRpcClient.cs @@ -8,12 +8,11 @@ using System.Threading.Tasks; using MQTTnet.Protocol; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public interface IMqttRpcClient : IDisposable { - public interface IMqttRpcClient : IDisposable - { - Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null); - - Task ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null, CancellationToken cancellationToken = default); - } + Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null); + + Task ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/IMqttRpcClientTopicGenerationStrategy.cs b/Source/MQTTnet.Extensions.Rpc/IMqttRpcClientTopicGenerationStrategy.cs index 487daa135..2a2746ad4 100644 --- a/Source/MQTTnet.Extensions.Rpc/IMqttRpcClientTopicGenerationStrategy.cs +++ b/Source/MQTTnet.Extensions.Rpc/IMqttRpcClientTopicGenerationStrategy.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public interface IMqttRpcClientTopicGenerationStrategy { - public interface IMqttRpcClientTopicGenerationStrategy - { - MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context); - } -} + MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context); +} \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs b/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs index 3de669545..cedc3a2fe 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs @@ -4,26 +4,25 @@ using System; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public static class MqttFactoryExtensions { - public static class MqttFactoryExtensions + public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient) { - public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient) - { - return clientFactory.CreateMqttRpcClient( - mqttClient, - new MqttRpcClientOptions - { - TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy() - }); - } + return clientFactory.CreateMqttRpcClient( + mqttClient, + new MqttRpcClientOptions + { + TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy() + }); + } - public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) - { - ArgumentNullException.ThrowIfNull(mqttClient); - ArgumentNullException.ThrowIfNull(rpcClientOptions); + public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) + { + ArgumentNullException.ThrowIfNull(mqttClient); + ArgumentNullException.ThrowIfNull(rpcClientOptions); - return new MqttRpcClient(mqttClient, rpcClientOptions); - } + return new MqttRpcClient(mqttClient, rpcClientOptions); } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index c64b93c2d..fc83bff18 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MQTTnet.Exceptions; @@ -14,128 +13,125 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public sealed class MqttRpcClient : IMqttRpcClient { - public sealed class MqttRpcClient : IMqttRpcClient + readonly IMqttClient _mqttClient; + readonly MqttRpcClientOptions _options; + + readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>(); + + public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options) { - readonly IMqttClient _mqttClient; - readonly MqttRpcClientOptions _options; + _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); + _options = options ?? throw new ArgumentNullException(nameof(options)); - readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>(); + _mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync; + } - public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options) - { - _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); - _options = options ?? throw new ArgumentNullException(nameof(options)); + public void Dispose() + { + _mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync; - _mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync; + foreach (var tcs in _waitingCalls) + { + tcs.Value.TrySetCanceled(); } - public void Dispose() - { - _mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync; + _waitingCalls.Clear(); + } - foreach (var tcs in _waitingCalls) + public async Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null) + { + using var timeoutToken = new CancellationTokenSource(timeout); + try + { + return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false); + } + catch (OperationCanceledException exception) + { + if (timeoutToken.IsCancellationRequested) { - tcs.Value.TrySetCanceled(); + throw new MqttCommunicationTimedOutException(exception); } - _waitingCalls.Clear(); + throw; } + } + + public async Task ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(methodName); - public async Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null) + var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel); + var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); + + var requestTopic = topicNames.RequestTopic; + var responseTopic = topicNames.ResponseTopic; + + if (string.IsNullOrWhiteSpace(requestTopic)) { - using (var timeoutToken = new CancellationTokenSource(timeout)) - { - try - { - return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false); - } - catch (OperationCanceledException exception) - { - if (timeoutToken.IsCancellationRequested) - { - throw new MqttCommunicationTimedOutException(exception); - } - - throw; - } - } + throw new MqttProtocolViolationException("RPC request topic is empty."); } - public async Task ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null, CancellationToken cancellationToken = default) + if (string.IsNullOrWhiteSpace(responseTopic)) { - ArgumentNullException.ThrowIfNull(methodName); - - var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel); - var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); + throw new MqttProtocolViolationException("RPC response topic is empty."); + } - var requestTopic = topicNames.RequestTopic; - var responseTopic = topicNames.ResponseTopic; + var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel); - if (string.IsNullOrWhiteSpace(requestTopic)) - { - throw new MqttProtocolViolationException("RPC request topic is empty."); - } + if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500) + { + requestMessageBuilder.WithResponseTopic(responseTopic); + } - if (string.IsNullOrWhiteSpace(responseTopic)) - { - throw new MqttProtocolViolationException("RPC response topic is empty."); - } + var requestMessage = requestMessageBuilder.Build(); - var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel); + try + { + var awaitable = new AsyncTaskCompletionSource(); - if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500) + if (!_waitingCalls.TryAdd(responseTopic, awaitable)) { - requestMessageBuilder.WithResponseTopic(responseTopic); + throw new InvalidOperationException(); } - var requestMessage = requestMessageBuilder.Build(); + var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build(); - try - { - var awaitable = new AsyncTaskCompletionSource(); - - if (!_waitingCalls.TryAdd(responseTopic, awaitable)) - { - throw new InvalidOperationException(); - } - - var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build(); - - await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false); - await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false); - - using (cancellationToken.Register( - () => - { - awaitable.TrySetCanceled(); - })) - { - return await awaitable.Task.ConfigureAwait(false); - } - } - finally + await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false); + await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false); + + using (cancellationToken.Register( + () => + { + awaitable.TrySetCanceled(); + })) { - _waitingCalls.TryRemove(responseTopic, out _); - await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false); + return await awaitable.Task.ConfigureAwait(false); } } + finally + { + _waitingCalls.TryRemove(responseTopic, out _); + await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false); + } + } - Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + { + if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable)) { - if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable)) - { - return CompletedTask.Instance; - } + return CompletedTask.Instance; + } - var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray(); - awaitable.TrySetResult(payloadBuffer); + var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray(); + awaitable.TrySetResult(payloadBuffer); - // Set this message to handled to that other code can avoid execution etc. - eventArgs.IsHandled = true; + // Set this message to handled to that other code can avoid execution etc. + eventArgs.IsHandled = true; - return CompletedTask.Instance; - } + return CompletedTask.Instance; } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClientExtensions.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClientExtensions.cs index a0b0ebfee..bde8aefcc 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClientExtensions.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClientExtensions.cs @@ -8,17 +8,16 @@ using System.Threading.Tasks; using MQTTnet.Protocol; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public static class MqttRpcClientExtensions { - public static class MqttRpcClientExtensions + public static Task ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null) { - public static Task ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary parameters = null) - { - if (client == null) throw new ArgumentNullException(nameof(client)); + if (client == null) throw new ArgumentNullException(nameof(client)); - var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); + var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); - return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters); - } + return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters); } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptions.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptions.cs index bb8917054..251282eed 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptions.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptions.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public sealed class MqttRpcClientOptions { - public sealed class MqttRpcClientOptions - { - public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy(); - } -} + public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy(); +} \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptionsBuilder.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptionsBuilder.cs index 5fb0ea2ea..e0566035e 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptionsBuilder.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClientOptionsBuilder.cs @@ -4,25 +4,24 @@ using System; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public sealed class MqttRpcClientOptionsBuilder { - public sealed class MqttRpcClientOptionsBuilder - { - IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy(); + IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy(); - public MqttRpcClientOptions Build() + public MqttRpcClientOptions Build() + { + return new MqttRpcClientOptions { - return new MqttRpcClientOptions - { - TopicGenerationStrategy = _topicGenerationStrategy - }; - } + TopicGenerationStrategy = _topicGenerationStrategy + }; + } - public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value) - { - _topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value)); + public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value) + { + _topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value)); - return this; - } + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcTopicPair.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcTopicPair.cs index 4cbb16603..2cdf7ffcd 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcTopicPair.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcTopicPair.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public sealed class MqttRpcTopicPair { - public sealed class MqttRpcTopicPair - { - public string RequestTopic { get; set; } + public string RequestTopic { get; set; } - public string ResponseTopic { get; set; } - } -} + public string ResponseTopic { get; set; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/TopicGenerationContext.cs b/Source/MQTTnet.Extensions.Rpc/TopicGenerationContext.cs index c40544d26..9559f638f 100644 --- a/Source/MQTTnet.Extensions.Rpc/TopicGenerationContext.cs +++ b/Source/MQTTnet.Extensions.Rpc/TopicGenerationContext.cs @@ -6,37 +6,36 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Extensions.Rpc +namespace MQTTnet.Extensions.Rpc; + +public sealed class TopicGenerationContext { - public sealed class TopicGenerationContext + public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, + IDictionary parameters, MqttQualityOfServiceLevel qualityOfServiceLevel) { - public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, - IDictionary parameters, MqttQualityOfServiceLevel qualityOfServiceLevel) - { - MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); - Parameters = parameters; - QualityOfServiceLevel = qualityOfServiceLevel; - MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); - Options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public string MethodName { get; } - - public IDictionary Parameters { get; } - - public IMqttClient MqttClient { get; } - - public MqttRpcClientOptions Options { get; } - - /// - /// Gets or sets the quality of service level. - /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message - /// that defines the guarantee of delivery for a specific message. - /// There are 3 QoS levels in MQTT: - /// - At most once (0): Message gets delivered no time, once or multiple times. - /// - At least once (1): Message gets delivered at least once (one time or more often). - /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). - /// - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } + MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); + Parameters = parameters; + QualityOfServiceLevel = qualityOfServiceLevel; + MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); + Options = options ?? throw new ArgumentNullException(nameof(options)); } + + public string MethodName { get; } + + public IDictionary Parameters { get; } + + public IMqttClient MqttClient { get; } + + public MqttRpcClientOptions Options { get; } + + /// + /// Gets or sets the quality of service level. + /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message + /// that defines the guarantee of delivery for a specific message. + /// There are 3 QoS levels in MQTT: + /// - At most once (0): Message gets delivered no time, once or multiple times. + /// - At least once (1): Message gets delivered at least once (one time or more often). + /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). + /// + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj b/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj index 5bfcfeb5c..01568c41c 100644 --- a/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj +++ b/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj @@ -47,7 +47,7 @@ - + diff --git a/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs b/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs index 66b7369eb..30dbf6e58 100644 --- a/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs +++ b/Source/MQTTnet.Extensions.TopicTemplate/MqttTopicTemplate.cs @@ -230,7 +230,7 @@ public bool MatchesTopic(string topic, bool subtree = false) throw new ArgumentException("the topic has to match this template", nameof(topic)); } - return parseParameterValuesInternal(topic); + return ParseParameterValuesInternal(topic); } /// @@ -330,7 +330,7 @@ public MqttTopicTemplate WithParameterValuesFrom(IEnumerable<(string parameter, return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value)); } - IEnumerable<(string parameter, int index, string value)> parseParameterValuesInternal(string topic) + IEnumerable<(string parameter, int index, string value)> ParseParameterValuesInternal(string topic) { // because we have a match, we know the segment array is at least the template's length var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator); diff --git a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs index 763217fb1..fe4c6be46 100644 --- a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs +++ b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs @@ -5,28 +5,27 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttServerClientDisconnectOptions { - public sealed class MqttServerClientDisconnectOptions - { - public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection; + public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection; - /// - /// The reason string is sent to every client via a DISCONNECT packet. - /// MQTT 5.0.0+ feature. - /// - public string ReasonString { get; set; } + /// + /// The reason string is sent to every client via a DISCONNECT packet. + /// MQTT 5.0.0+ feature. + /// + public string ReasonString { get; set; } - /// - /// The server reference is sent to every client via a DISCONNECT packet. - /// MQTT 5.0.0+ feature. - /// - public string ServerReference { get; set; } + /// + /// The server reference is sent to every client via a DISCONNECT packet. + /// MQTT 5.0.0+ feature. + /// + public string ServerReference { get; set; } - /// - /// These user properties are sent to every client via a DISCONNECT packet. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties { get; set; } - } + /// + /// These user properties are sent to every client via a DISCONNECT packet. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs index 7173ac64b..8cac4369e 100644 --- a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs +++ b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs @@ -5,50 +5,49 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttServerClientDisconnectOptionsBuilder { - public sealed class MqttServerClientDisconnectOptionsBuilder + readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions(); + + public MqttServerClientDisconnectOptions Build() { - readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions(); + return _options; + } - public MqttServerClientDisconnectOptions Build() - { - return _options; - } + public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value) + { + _options.ReasonCode = value; + return this; + } - public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value) - { - _options.ReasonCode = value; - return this; - } + public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value) + { + _options.ReasonString = value; + return this; + } - public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value) - { - _options.ReasonString = value; - return this; - } + public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value) + { + _options.ServerReference = value; + return this; + } - public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value) - { - _options.ServerReference = value; - return this; - } + public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List value) + { + _options.UserProperties = value; + return this; + } - public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List value) + public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value) + { + if (_options.UserProperties == null) { - _options.UserProperties = value; - return this; + _options.UserProperties = new List(); } - public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value) - { - if (_options.UserProperties == null) - { - _options.UserProperties = new List(); - } - - _options.UserProperties.Add(new MqttUserProperty(name, value)); - return this; - } + _options.UserProperties.Add(new MqttUserProperty(name, value)); + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ApplicationMessageEnqueuedEventArgs.cs b/Source/MQTTnet.Server/Events/ApplicationMessageEnqueuedEventArgs.cs index 518c0a58e..a85b1321f 100644 --- a/Source/MQTTnet.Server/Events/ApplicationMessageEnqueuedEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ApplicationMessageEnqueuedEventArgs.cs @@ -2,26 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs { - public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs + public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped) { - public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped) - { - SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId)); - ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - IsDropped = isDropped; - } + SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId)); + ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + IsDropped = isDropped; + } - public string SenderClientId { get; } + public string SenderClientId { get; } - public string ReceiverClientId { get; } + public string ReceiverClientId { get; } - public bool IsDropped { get; } + public bool IsDropped { get; } - public MqttApplicationMessage ApplicationMessage { get; } - } + public MqttApplicationMessage ApplicationMessage { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ApplicationMessageNotConsumedEventArgs.cs b/Source/MQTTnet.Server/Events/ApplicationMessageNotConsumedEventArgs.cs index d81e6bb74..212572f38 100644 --- a/Source/MQTTnet.Server/Events/ApplicationMessageNotConsumedEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ApplicationMessageNotConsumedEventArgs.cs @@ -2,26 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs { - public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs + public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId) { - public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId) - { - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - SenderId = senderId; - } + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + SenderId = senderId; + } - /// - /// Gets the application message which was not consumed by any client. - /// - public MqttApplicationMessage ApplicationMessage { get; } + /// + /// Gets the application message which was not consumed by any client. + /// + public MqttApplicationMessage ApplicationMessage { get; } - /// - /// Gets the ID of the client which has sent the affected application message. - /// - public string SenderId { get; } - } + /// + /// Gets the ID of the client which has sent the affected application message. + /// + public string SenderId { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ClientAcknowledgedPublishPacketEventArgs.cs b/Source/MQTTnet.Server/Events/ClientAcknowledgedPublishPacketEventArgs.cs index 8a206cc2f..316c86412 100644 --- a/Source/MQTTnet.Server/Events/ClientAcknowledgedPublishPacketEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ClientAcknowledgedPublishPacketEventArgs.cs @@ -2,51 +2,49 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs { - public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs + public ClientAcknowledgedPublishPacketEventArgs(string clientId, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket) { - public ClientAcknowledgedPublishPacketEventArgs(string clientId, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket) - { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - UserName = userName; - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); - AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket)); - } - - /// - /// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet. - /// - public MqttPacketWithIdentifier AcknowledgePacket { get; } - - /// - /// Gets the ID of the client which acknowledged a PUBLISH packet. - /// - public string ClientId { get; } - - /// - /// Gets the user name of the client. - /// - public string UserName { get; } - - /// - /// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2. - /// - public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket; - - /// - /// Gets the PUBLISH packet which was acknowledged. - /// - public MqttPublishPacket PublishPacket { get; } - - /// - /// Gets the session items which contain custom user data per session. - /// - public IDictionary SessionItems { get; } + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + UserName = userName; + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); + PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); + AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket)); } + + /// + /// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet. + /// + public MqttPacketWithIdentifier AcknowledgePacket { get; } + + /// + /// Gets the ID of the client which acknowledged a PUBLISH packet. + /// + public string ClientId { get; } + + /// + /// Gets the user name of the client. + /// + public string UserName { get; } + + /// + /// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2. + /// + public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket; + + /// + /// Gets the PUBLISH packet which was acknowledged. + /// + public MqttPublishPacket PublishPacket { get; } + + /// + /// Gets the session items which contain custom user data per session. + /// + public IDictionary SessionItems { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ClientConnectedEventArgs.cs b/Source/MQTTnet.Server/Events/ClientConnectedEventArgs.cs index 4249eea6f..718ecb659 100644 --- a/Source/MQTTnet.Server/Events/ClientConnectedEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ClientConnectedEventArgs.cs @@ -2,70 +2,67 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; using System.Net; using System.Text; using MQTTnet.Formatter; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class ClientConnectedEventArgs : EventArgs { - public sealed class ClientConnectedEventArgs : EventArgs - { - readonly MqttConnectPacket _connectPacket; + readonly MqttConnectPacket _connectPacket; - public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems) - { - _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); - ProtocolVersion = protocolVersion; - RemoteEndPoint = remoteEndPoint; - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - } + public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems) + { + _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); + ProtocolVersion = protocolVersion; + RemoteEndPoint = remoteEndPoint; + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); + } - public byte[] AuthenticationData => _connectPacket.AuthenticationData; + public byte[] AuthenticationData => _connectPacket.AuthenticationData; - public string AuthenticationMethod => _connectPacket.AuthenticationMethod; + public string AuthenticationMethod => _connectPacket.AuthenticationMethod; - /// - /// Gets the client identifier of the connected client. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId => _connectPacket.ClientId; + /// + /// Gets the client identifier of the connected client. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId => _connectPacket.ClientId; - /// - /// Gets the endpoint of the connected client. - /// - public EndPoint RemoteEndPoint { get; } + /// + /// Gets the endpoint of the connected client. + /// + public EndPoint RemoteEndPoint { get; } - [Obsolete("Use RemoteEndPoint instead.")] - public string Endpoint => RemoteEndPoint?.ToString(); + [Obsolete("Use RemoteEndPoint instead.")] + public string Endpoint => RemoteEndPoint?.ToString(); - /// - /// Gets the protocol version which is used by the connected client. - /// - public MqttProtocolVersion ProtocolVersion { get; } + /// + /// Gets the protocol version which is used by the connected client. + /// + public MqttProtocolVersion ProtocolVersion { get; } - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } - /// - /// Gets the user name of the connected client. - /// - public string UserName => _connectPacket.Username; + /// + /// Gets the user name of the connected client. + /// + public string UserName => _connectPacket.Username; - /// - /// Gets the password of the connected client. - /// - public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan()); + /// + /// Gets the password of the connected client. + /// + public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan()); - /// - /// Gets the user properties sent by the client. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties => _connectPacket?.UserProperties; - } + /// + /// Gets the user properties sent by the client. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties => _connectPacket?.UserProperties; } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ClientDisconnectedEventArgs.cs b/Source/MQTTnet.Server/Events/ClientDisconnectedEventArgs.cs index f64a2f3bd..9cb85202f 100644 --- a/Source/MQTTnet.Server/Events/ClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ClientDisconnectedEventArgs.cs @@ -2,91 +2,88 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; using System.Net; using System.Text; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class ClientDisconnectedEventArgs : EventArgs { - public sealed class ClientDisconnectedEventArgs : EventArgs + readonly MqttConnectPacket _connectPacket; + readonly MqttDisconnectPacket _disconnectPacket; + + public ClientDisconnectedEventArgs( + MqttConnectPacket connectPacket, + MqttDisconnectPacket disconnectPacket, + MqttClientDisconnectType disconnectType, + EndPoint remoteEndPoint, + IDictionary sessionItems) { - readonly MqttConnectPacket _connectPacket; - readonly MqttDisconnectPacket _disconnectPacket; - - public ClientDisconnectedEventArgs( - MqttConnectPacket connectPacket, - MqttDisconnectPacket disconnectPacket, - MqttClientDisconnectType disconnectType, - EndPoint remoteEndPoint, - IDictionary sessionItems) - { - DisconnectType = disconnectType; - RemoteEndPoint = remoteEndPoint; - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - - _connectPacket = connectPacket; - // The DISCONNECT packet can be null in case of a non clean disconnect or session takeover. - _disconnectPacket = disconnectPacket; - } - - /// - /// Gets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId => _connectPacket.ClientId; - - /// - /// Gets the user name of the client. - /// - public string UserName => _connectPacket.Username; - - /// - /// Gets the password of the client. - /// - public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan()); - - public MqttClientDisconnectType DisconnectType { get; } - - public EndPoint RemoteEndPoint { get; } - - [Obsolete("Use RemoteEndPoint instead.")] - public string Endpoint => RemoteEndPoint?.ToString(); - - /// - /// Gets the reason code sent by the client. - /// Only available for clean disconnects. - /// MQTT 5.0.0+ feature. - /// - public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode; - - /// - /// Gets the reason string sent by the client. - /// Only available for clean disconnects. - /// MQTT 5.0.0+ feature. - /// - public string ReasonString => _disconnectPacket?.ReasonString; - - /// - /// Gets the session expiry interval sent by the client. - /// Only available for clean disconnects. - /// MQTT 5.0.0+ feature. - /// - public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0; - - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } - - /// - /// Gets the user properties sent by the client. - /// Only available for clean disconnects. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties => _disconnectPacket?.UserProperties; + DisconnectType = disconnectType; + RemoteEndPoint = remoteEndPoint; + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); + + _connectPacket = connectPacket; + // The DISCONNECT packet can be null in case of a non clean disconnect or session takeover. + _disconnectPacket = disconnectPacket; } + + /// + /// Gets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId => _connectPacket.ClientId; + + /// + /// Gets the user name of the client. + /// + public string UserName => _connectPacket.Username; + + /// + /// Gets the password of the client. + /// + public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan()); + + public MqttClientDisconnectType DisconnectType { get; } + + public EndPoint RemoteEndPoint { get; } + + [Obsolete("Use RemoteEndPoint instead.")] + public string Endpoint => RemoteEndPoint?.ToString(); + + /// + /// Gets the reason code sent by the client. + /// Only available for clean disconnects. + /// MQTT 5.0.0+ feature. + /// + public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode; + + /// + /// Gets the reason string sent by the client. + /// Only available for clean disconnects. + /// MQTT 5.0.0+ feature. + /// + public string ReasonString => _disconnectPacket?.ReasonString; + + /// + /// Gets the session expiry interval sent by the client. + /// Only available for clean disconnects. + /// MQTT 5.0.0+ feature. + /// + public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0; + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } + + /// + /// Gets the user properties sent by the client. + /// Only available for clean disconnects. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties => _disconnectPacket?.UserProperties; } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ClientSubscribedTopicEventArgs.cs b/Source/MQTTnet.Server/Events/ClientSubscribedTopicEventArgs.cs index 2f5246835..e11f05ca5 100644 --- a/Source/MQTTnet.Server/Events/ClientSubscribedTopicEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ClientSubscribedTopicEventArgs.cs @@ -2,42 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class ClientSubscribedTopicEventArgs : EventArgs { - public sealed class ClientSubscribedTopicEventArgs : EventArgs + public ClientSubscribedTopicEventArgs(string clientId, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems) { - public ClientSubscribedTopicEventArgs(string clientId, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems) - { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - UserName = userName; - TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - } + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + UserName = userName; + TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); + } - /// - /// Gets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId { get; } + /// + /// Gets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId { get; } - /// - /// Gets the user name of the client. - /// - public string UserName { get; } + /// + /// Gets the user name of the client. + /// + public string UserName { get; } - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } - /// - /// Gets the topic filter. - /// The topic filter can contain topics and wildcards. - /// - public MqttTopicFilter TopicFilter { get; } - } + /// + /// Gets the topic filter. + /// The topic filter can contain topics and wildcards. + /// + public MqttTopicFilter TopicFilter { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/ClientUnsubscribedTopicEventArgs.cs b/Source/MQTTnet.Server/Events/ClientUnsubscribedTopicEventArgs.cs index 677236b37..484c1b730 100644 --- a/Source/MQTTnet.Server/Events/ClientUnsubscribedTopicEventArgs.cs +++ b/Source/MQTTnet.Server/Events/ClientUnsubscribedTopicEventArgs.cs @@ -2,41 +2,39 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class ClientUnsubscribedTopicEventArgs : EventArgs { - public sealed class ClientUnsubscribedTopicEventArgs : EventArgs + public ClientUnsubscribedTopicEventArgs(string clientId, string userName, string topicFilter, IDictionary sessionItems) { - public ClientUnsubscribedTopicEventArgs(string clientId, string userName, string topicFilter, IDictionary sessionItems) - { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - UserName = userName; - TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - } + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + UserName = userName; + TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); + } - /// - /// Gets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId { get; } + /// + /// Gets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId { get; } - /// - /// Gets the user name of the client. - /// - public string UserName { get; } + /// + /// Gets the user name of the client. + /// + public string UserName { get; } - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } - /// - /// Gets or sets the topic filter. - /// The topic filter can contain topics and wildcards. - /// - public string TopicFilter { get; } - } + /// + /// Gets or sets the topic filter. + /// The topic filter can contain topics and wildcards. + /// + public string TopicFilter { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/InterceptingClientApplicationMessageEnqueueEventArgs.cs b/Source/MQTTnet.Server/Events/InterceptingClientApplicationMessageEnqueueEventArgs.cs index eb8e45fe1..71447ecaf 100644 --- a/Source/MQTTnet.Server/Events/InterceptingClientApplicationMessageEnqueueEventArgs.cs +++ b/Source/MQTTnet.Server/Events/InterceptingClientApplicationMessageEnqueueEventArgs.cs @@ -2,34 +2,31 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs { - public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs + public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage) { - public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage) - { - SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId)); - ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - } + SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId)); + ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + } - /// - /// Gets or sets whether the enqueue of the application message should be performed or not. - /// If set to _False_ the client will not receive the application message. - /// - public bool AcceptEnqueue { get; set; } = true; + /// + /// Gets or sets whether the enqueue of the application message should be performed or not. + /// If set to _False_ the client will not receive the application message. + /// + public bool AcceptEnqueue { get; set; } = true; - public MqttApplicationMessage ApplicationMessage { get; } + public MqttApplicationMessage ApplicationMessage { get; } - /// - /// Indicates if the connection with the sender should be closed. - /// - public bool CloseSenderConnection { get; set; } + /// + /// Indicates if the connection with the sender should be closed. + /// + public bool CloseSenderConnection { get; set; } - public string ReceiverClientId { get; } + public string ReceiverClientId { get; } - public string SenderClientId { get; } - } + public string SenderClientId { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/InterceptingPacketEventArgs.cs b/Source/MQTTnet.Server/Events/InterceptingPacketEventArgs.cs index 6067a17e4..d266d1a22 100644 --- a/Source/MQTTnet.Server/Events/InterceptingPacketEventArgs.cs +++ b/Source/MQTTnet.Server/Events/InterceptingPacketEventArgs.cs @@ -2,63 +2,60 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; using System.Net; -using System.Threading; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class InterceptingPacketEventArgs : EventArgs { - public sealed class InterceptingPacketEventArgs : EventArgs + public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string userName, EndPoint remoteEndPoint, MqttPacket packet, IDictionary sessionItems) { - public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string userName, EndPoint remoteEndPoint, MqttPacket packet, IDictionary sessionItems) - { - CancellationToken = cancellationToken; - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - UserName = userName; - RemoteEndPoint = remoteEndPoint; - Packet = packet ?? throw new ArgumentNullException(nameof(packet)); - SessionItems = sessionItems; - } - - /// - /// Gets the cancellation token from the connection managing thread. - /// Use this in further event processing. - /// - public CancellationToken CancellationToken { get; } - - /// - /// Gets the client ID which has sent the packet or will receive the packet. - /// - public string ClientId { get; } - - /// - /// Gets the user name of the client. - /// - public string UserName { get; } - - /// - /// Gets the endpoint of the sending or receiving client. - /// - public EndPoint RemoteEndPoint { get; } - - [Obsolete("Use RemoteEndPoint instead.")] - public string Endpoint => RemoteEndPoint?.ToString(); - - /// - /// Gets or sets the MQTT packet which was received or will be sent. - /// - public MqttPacket Packet { get; set; } - - /// - /// Gets or sets whether the packet should be processed or not. - /// - public bool ProcessPacket { get; set; } = true; - - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } + CancellationToken = cancellationToken; + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + UserName = userName; + RemoteEndPoint = remoteEndPoint; + Packet = packet ?? throw new ArgumentNullException(nameof(packet)); + SessionItems = sessionItems; } + + /// + /// Gets the cancellation token from the connection managing thread. + /// Use this in further event processing. + /// + public CancellationToken CancellationToken { get; } + + /// + /// Gets the client ID which has sent the packet or will receive the packet. + /// + public string ClientId { get; } + + /// + /// Gets the user name of the client. + /// + public string UserName { get; } + + /// + /// Gets the endpoint of the sending or receiving client. + /// + public EndPoint RemoteEndPoint { get; } + + [Obsolete("Use RemoteEndPoint instead.")] + public string Endpoint => RemoteEndPoint?.ToString(); + + /// + /// Gets or sets the MQTT packet which was received or will be sent. + /// + public MqttPacket Packet { get; set; } + + /// + /// Gets or sets whether the packet should be processed or not. + /// + public bool ProcessPacket { get; set; } = true; + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/InterceptingPublishEventArgs.cs b/Source/MQTTnet.Server/Events/InterceptingPublishEventArgs.cs index b20961049..9b93112a0 100644 --- a/Source/MQTTnet.Server/Events/InterceptingPublishEventArgs.cs +++ b/Source/MQTTnet.Server/Events/InterceptingPublishEventArgs.cs @@ -2,56 +2,53 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -using System.Threading; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class InterceptingPublishEventArgs : EventArgs { - public sealed class InterceptingPublishEventArgs : EventArgs + public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems) { - public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems) - { - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - CancellationToken = cancellationToken; - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - UserName = userName; - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - } - - public MqttApplicationMessage ApplicationMessage { get; set; } - - /// - /// Gets the cancellation token which can indicate that the client connection gets down. - /// - public CancellationToken CancellationToken { get; } - - /// - /// Gets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId { get; } - - /// - /// Gets the user name of the client. - /// - public string UserName { get; } - - public bool CloseConnection { get; set; } - - /// - /// Gets or sets whether the publish should be processed internally. - /// - public bool ProcessPublish { get; set; } = true; - - /// - /// Gets the response which will be sent to the client via the PUBACK etc. packets. - /// - public PublishResponse Response { get; } = new PublishResponse(); - - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + CancellationToken = cancellationToken; + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + UserName = userName; + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); } + + public MqttApplicationMessage ApplicationMessage { get; set; } + + /// + /// Gets the cancellation token which can indicate that the client connection gets down. + /// + public CancellationToken CancellationToken { get; } + + /// + /// Gets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId { get; } + + /// + /// Gets the user name of the client. + /// + public string UserName { get; } + + public bool CloseConnection { get; set; } + + /// + /// Gets or sets whether the publish should be processed internally. + /// + public bool ProcessPublish { get; set; } = true; + + /// + /// Gets the response which will be sent to the client via the PUBACK etc. packets. + /// + public PublishResponse Response { get; } = new PublishResponse(); + + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/InterceptingSubscriptionEventArgs.cs b/Source/MQTTnet.Server/Events/InterceptingSubscriptionEventArgs.cs index 7f62d68e9..69c532c9f 100644 --- a/Source/MQTTnet.Server/Events/InterceptingSubscriptionEventArgs.cs +++ b/Source/MQTTnet.Server/Events/InterceptingSubscriptionEventArgs.cs @@ -2,90 +2,86 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; -using System.Threading; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class InterceptingSubscriptionEventArgs : EventArgs { - public sealed class InterceptingSubscriptionEventArgs : EventArgs + public InterceptingSubscriptionEventArgs( + CancellationToken cancellationToken, + string clientId, + string userName, + MqttSessionStatus session, + MqttTopicFilter topicFilter, + List userProperties) { - public InterceptingSubscriptionEventArgs( - CancellationToken cancellationToken, - string clientId, - string userName, - MqttSessionStatus session, - MqttTopicFilter topicFilter, - List userProperties) - { - CancellationToken = cancellationToken; - ClientId = clientId; - UserName = userName; - Session = session; - TopicFilter = topicFilter; - UserProperties = userProperties; - } + CancellationToken = cancellationToken; + ClientId = clientId; + UserName = userName; + Session = session; + TopicFilter = topicFilter; + UserProperties = userProperties; + } - /// - /// Gets the cancellation token which can indicate that the client connection gets down. - /// - public CancellationToken CancellationToken { get; } + /// + /// Gets the cancellation token which can indicate that the client connection gets down. + /// + public CancellationToken CancellationToken { get; } - /// - /// Gets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId { get; } + /// + /// Gets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId { get; } - /// - /// Gets the user name of the client. - /// - public string UserName { get; } + /// + /// Gets the user name of the client. + /// + public string UserName { get; } - /// - /// Gets or sets whether the broker should close the client connection. - /// - public bool CloseConnection { get; set; } + /// + /// Gets or sets whether the broker should close the client connection. + /// + public bool CloseConnection { get; set; } - /// - /// Gets or sets whether the broker should create an internal subscription for the client. - /// The broker can also avoid this and return "success" to the client. - /// This feature allows using the MQTT Broker as the Frontend and another system as the backend. - /// - public bool ProcessSubscription { get; set; } = true; + /// + /// Gets or sets whether the broker should create an internal subscription for the client. + /// The broker can also avoid this and return "success" to the client. + /// This feature allows using the MQTT Broker as the Frontend and another system as the backend. + /// + public bool ProcessSubscription { get; set; } = true; - /// - /// Gets or sets the reason string which will be sent to the client in the SUBACK packet. - /// - public string ReasonString { get; set; } + /// + /// Gets or sets the reason string which will be sent to the client in the SUBACK packet. + /// + public string ReasonString { get; set; } - /// - /// Gets the response which will be sent to the client via the SUBACK packet. - /// - public SubscribeResponse Response { get; } = new SubscribeResponse(); + /// + /// Gets the response which will be sent to the client via the SUBACK packet. + /// + public SubscribeResponse Response { get; } = new SubscribeResponse(); - /// - /// Gets the current client session. - /// - public MqttSessionStatus Session { get; } + /// + /// Gets the current client session. + /// + public MqttSessionStatus Session { get; } - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems => Session.Items; + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems => Session.Items; - /// - /// Gets or sets the topic filter. - /// The topic filter can contain topics and wildcards. - /// - public MqttTopicFilter TopicFilter { get; set; } + /// + /// Gets or sets the topic filter. + /// The topic filter can contain topics and wildcards. + /// + public MqttTopicFilter TopicFilter { get; set; } - /// - /// Gets or sets the user properties. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties { get; set; } - } + /// + /// Gets or sets the user properties. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/InterceptingUnsubscriptionEventArgs.cs b/Source/MQTTnet.Server/Events/InterceptingUnsubscriptionEventArgs.cs index 5838aadcb..053fdbb42 100644 --- a/Source/MQTTnet.Server/Events/InterceptingUnsubscriptionEventArgs.cs +++ b/Source/MQTTnet.Server/Events/InterceptingUnsubscriptionEventArgs.cs @@ -2,77 +2,73 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; -using System.Threading; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class InterceptingUnsubscriptionEventArgs : EventArgs { - public sealed class InterceptingUnsubscriptionEventArgs : EventArgs + public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems, string topic, List userProperties) { - public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems, string topic, List userProperties) - { - CancellationToken = cancellationToken; - ClientId = clientId; - UserName = userName; - SessionItems = sessionItems; - Topic = topic; - UserProperties = userProperties; - } + CancellationToken = cancellationToken; + ClientId = clientId; + UserName = userName; + SessionItems = sessionItems; + Topic = topic; + UserProperties = userProperties; + } - /// - /// Gets the cancellation token which can indicate that the client connection gets down. - /// - public CancellationToken CancellationToken { get; } + /// + /// Gets the cancellation token which can indicate that the client connection gets down. + /// + public CancellationToken CancellationToken { get; } - /// - /// Gets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string ClientId { get; } + /// + /// Gets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string ClientId { get; } - /// - /// Gets the user name of the client. - /// - public string UserName { get; } + /// + /// Gets the user name of the client. + /// + public string UserName { get; } - /// - /// Gets or sets whether the broker should close the client connection. - /// - public bool CloseConnection { get; set; } + /// + /// Gets or sets whether the broker should close the client connection. + /// + public bool CloseConnection { get; set; } - /// - /// Gets or sets whether the broker should remove an internal subscription for the client. - /// The broker can also avoid this and return "success" to the client. - /// This feature allows using the MQTT Broker as the Frontend and another system as the backend. - /// - public bool ProcessUnsubscription { get; set; } = true; + /// + /// Gets or sets whether the broker should remove an internal subscription for the client. + /// The broker can also avoid this and return "success" to the client. + /// This feature allows using the MQTT Broker as the Frontend and another system as the backend. + /// + public bool ProcessUnsubscription { get; set; } = true; - /// - /// Gets the response which will be sent to the client via the UNSUBACK pocket. - /// - public UnsubscribeResponse Response { get; } = new UnsubscribeResponse(); + /// + /// Gets the response which will be sent to the client via the UNSUBACK pocket. + /// + public UnsubscribeResponse Response { get; } = new UnsubscribeResponse(); - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } - /// - /// Gets or sets the MQTT topic. - /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected - /// client. - /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level - /// separator). - /// - public string Topic { get; } + /// + /// Gets or sets the MQTT topic. + /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected + /// client. + /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level + /// separator). + /// + public string Topic { get; } - /// - /// Gets or sets the user properties. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties { get; set; } - } + /// + /// Gets or sets the user properties. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/LoadingRetainedMessagesEventArgs.cs b/Source/MQTTnet.Server/Events/LoadingRetainedMessagesEventArgs.cs index 12aa04692..99b1ea51f 100644 --- a/Source/MQTTnet.Server/Events/LoadingRetainedMessagesEventArgs.cs +++ b/Source/MQTTnet.Server/Events/LoadingRetainedMessagesEventArgs.cs @@ -2,13 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class LoadingRetainedMessagesEventArgs : EventArgs { - public sealed class LoadingRetainedMessagesEventArgs : EventArgs - { - public List LoadedRetainedMessages { get; set; } = new List(); - } + public List LoadedRetainedMessages { get; set; } = new List(); } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs b/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs index d75ed503a..e2ee068d8 100644 --- a/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs +++ b/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs @@ -2,50 +2,47 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using MQTTnet.Packets; using MQTTnet.Server.Internal; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class PreparingSessionEventArgs : EventArgs { - public sealed class PreparingSessionEventArgs : EventArgs - { - public string Id { get; set; } + public string Id { get; set; } - // TODO: Allow adding of packets to the queue etc. + // TODO: Allow adding of packets to the queue etc. - /* - * The Session State in the Server consists of: + /* + * The Session State in the Server consists of: · The existence of a Session, even if the rest of the Session State is empty. · The Clients subscriptions, including any Subscription Identifiers. · QoS 1 and QoS 2 messages which have been sent to the Client, but have not been completely acknowledged. · QoS 1 and QoS 2 messages pending transmission to the Client and OPTIONALLY QoS 0 messages pending transmission to the Client. · QoS 2 messages which have been received from the Client, but have not been completely acknowledged.The Will Message and the Will Delay Interval · If the Session is currently not connected, the time at which the Session will end and Session State will be discarded. - */ + */ - public bool IsExistingSession { get; set; } + public bool IsExistingSession { get; set; } - public IDictionary Items { get; set; } + public IDictionary Items { get; set; } - public List PublishPackets { get; } = new List(); + public List PublishPackets { get; } = new List(); - DateTime? SessionExpiryTimestamp { get; set; } + DateTime? SessionExpiryTimestamp { get; set; } - public List Subscriptions { get; } = new List(); + public List Subscriptions { get; } = new List(); - /// - /// Gets the will delay interval. - /// This is the time between the client disconnect and the time the will message will be sent. - /// - public uint? WillDelayInterval { get; set; } + /// + /// Gets the will delay interval. + /// This is the time between the client disconnect and the time the will message will be sent. + /// + public uint? WillDelayInterval { get; set; } - // - // Gets the last will message. - // In MQTT, you use the last will message feature to notify other clients about an ungracefully disconnected client. - // - // TODO: Use single properties. No entire will message. - //MqttApplicationMessage WillMessage { get; set; } - } + // + // Gets the last will message. + // In MQTT, you use the last will message feature to notify other clients about an ungracefully disconnected client. + // + // TODO: Use single properties. No entire will message. + //MqttApplicationMessage WillMessage { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/QueueMessageOverwrittenEventArgs.cs b/Source/MQTTnet.Server/Events/QueueMessageOverwrittenEventArgs.cs index 161e50548..7f28cddda 100644 --- a/Source/MQTTnet.Server/Events/QueueMessageOverwrittenEventArgs.cs +++ b/Source/MQTTnet.Server/Events/QueueMessageOverwrittenEventArgs.cs @@ -2,21 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class QueueMessageOverwrittenEventArgs : EventArgs { - public sealed class QueueMessageOverwrittenEventArgs : EventArgs + public QueueMessageOverwrittenEventArgs(string receiverClientId, MqttPacket packet) { - public QueueMessageOverwrittenEventArgs(string receiverClientId, MqttPacket packet) - { - ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); - Packet = packet ?? throw new ArgumentNullException(nameof(packet)); - } + ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); + Packet = packet ?? throw new ArgumentNullException(nameof(packet)); + } - public MqttPacket Packet { get; } + public MqttPacket Packet { get; } - public string ReceiverClientId { get; } - } + public string ReceiverClientId { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/RetainedMessageChangedEventArgs.cs b/Source/MQTTnet.Server/Events/RetainedMessageChangedEventArgs.cs index c7beb8076..61c21cd47 100644 --- a/Source/MQTTnet.Server/Events/RetainedMessageChangedEventArgs.cs +++ b/Source/MQTTnet.Server/Events/RetainedMessageChangedEventArgs.cs @@ -2,24 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class RetainedMessageChangedEventArgs : EventArgs { - public sealed class RetainedMessageChangedEventArgs : EventArgs + public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List storedRetainedMessages) { - public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List storedRetainedMessages) - { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage)); - StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages)); - } + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage)); + StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages)); + } - public MqttApplicationMessage ChangedRetainedMessage { get; } + public MqttApplicationMessage ChangedRetainedMessage { get; } - public string ClientId { get; } + public string ClientId { get; } - public List StoredRetainedMessages { get; } - } + public List StoredRetainedMessages { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Events/SessionDeletedEventArgs.cs b/Source/MQTTnet.Server/Events/SessionDeletedEventArgs.cs index dd9037239..4819de919 100644 --- a/Source/MQTTnet.Server/Events/SessionDeletedEventArgs.cs +++ b/Source/MQTTnet.Server/Events/SessionDeletedEventArgs.cs @@ -2,33 +2,31 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class SessionDeletedEventArgs : EventArgs { - public sealed class SessionDeletedEventArgs : EventArgs + public SessionDeletedEventArgs(string id, string userName, IDictionary sessionItems) { - public SessionDeletedEventArgs(string id, string userName, IDictionary sessionItems) - { - Id = id ?? throw new ArgumentNullException(nameof(id)); - UserName = userName; - SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); - } + Id = id ?? throw new ArgumentNullException(nameof(id)); + UserName = userName; + SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); + } - /// - /// Gets the ID of the session. - /// - public string Id { get; } + /// + /// Gets the ID of the session. + /// + public string Id { get; } - /// - /// Gets the user name of the session. - /// - public string UserName { get; } + /// + /// Gets the user name of the session. + /// + public string UserName { get; } - /// - /// Gets or sets a key/value collection that can be used to share data within the scope of this session. - /// - public IDictionary SessionItems { get; } - } + /// + /// Gets or sets a key/value collection that can be used to share data within the scope of this session. + /// + public IDictionary SessionItems { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs index 7d2e716fc..9c955a03c 100644 --- a/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs +++ b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs @@ -8,118 +8,117 @@ using MQTTnet.Diagnostics.Logger; using MQTTnet.Internal; -namespace MQTTnet.Server.Internal.Adapter +namespace MQTTnet.Server.Internal.Adapter; + +public sealed class MqttTcpServerAdapter : IMqttServerAdapter { - public sealed class MqttTcpServerAdapter : IMqttServerAdapter - { - readonly List _listeners = new List(); + readonly List _listeners = new List(); + + CancellationTokenSource _cancellationTokenSource; - CancellationTokenSource _cancellationTokenSource; + MqttServerOptions _serverOptions; - MqttServerOptions _serverOptions; + public Func ClientHandler { get; set; } - public Func ClientHandler { get; set; } + public bool TreatSocketOpeningErrorAsWarning { get; set; } - public bool TreatSocketOpeningErrorAsWarning { get; set; } + public void Dispose() + { + Cleanup(); + } - public void Dispose() + public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) + { + if (_cancellationTokenSource != null) { - Cleanup(); + throw new InvalidOperationException("Server is already started."); } - public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) - { - if (_cancellationTokenSource != null) - { - throw new InvalidOperationException("Server is already started."); - } + _serverOptions = options; - _serverOptions = options; + _cancellationTokenSource = new CancellationTokenSource(); - _cancellationTokenSource = new CancellationTokenSource(); + if (options.DefaultEndpointOptions.IsEnabled) + { + RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token); + } - if (options.DefaultEndpointOptions.IsEnabled) + if (options.TlsEndpointOptions?.IsEnabled == true) + { + if (options.TlsEndpointOptions.CertificateProvider == null) { - RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token); + throw new ArgumentException("TLS certificate is not set."); } - if (options.TlsEndpointOptions?.IsEnabled == true) - { - if (options.TlsEndpointOptions.CertificateProvider == null) - { - throw new ArgumentException("TLS certificate is not set."); - } + RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token); + } - RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token); - } + return CompletedTask.Instance; + } - return CompletedTask.Instance; - } + public Task StopAsync() + { + Cleanup(); + return CompletedTask.Instance; + } - public Task StopAsync() + void Cleanup() + { + try { - Cleanup(); - return CompletedTask.Instance; + _cancellationTokenSource?.Cancel(false); } - - void Cleanup() + finally { - try + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + + foreach (var listener in _listeners) { - _cancellationTokenSource?.Cancel(false); + listener.Dispose(); } - finally - { - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; - foreach (var listener in _listeners) - { - listener.Dispose(); - } + _listeners.Clear(); + } + } - _listeners.Clear(); - } + Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter) + { + var clientHandler = ClientHandler; + if (clientHandler == null) + { + return CompletedTask.Instance; } - Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter) + return clientHandler(channelAdapter); + } + + void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken) + { + if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None)) { - var clientHandler = ClientHandler; - if (clientHandler == null) + var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger) { - return CompletedTask.Instance; - } + ClientHandler = OnClientAcceptedAsync + }; - return clientHandler(channelAdapter); + if (listenerV4.Start(TreatSocketOpeningErrorAsWarning, cancellationToken)) + { + _listeners.Add(listenerV4); + } } - void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken) + if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None)) { - if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None)) + var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger) { - var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger) - { - ClientHandler = OnClientAcceptedAsync - }; - - if (listenerV4.Start(TreatSocketOpeningErrorAsWarning, cancellationToken)) - { - _listeners.Add(listenerV4); - } - } + ClientHandler = OnClientAcceptedAsync + }; - if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None)) + if (listenerV6.Start(TreatSocketOpeningErrorAsWarning, cancellationToken)) { - var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger) - { - ClientHandler = OnClientAcceptedAsync - }; - - if (listenerV6.Start(TreatSocketOpeningErrorAsWarning, cancellationToken)) - { - _listeners.Add(listenerV6); - } + _listeners.Add(listenerV6); } } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs index 7a0453ce8..e539dc60f 100644 --- a/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs +++ b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs @@ -12,248 +12,247 @@ using MQTTnet.Implementations; using MQTTnet.Internal; -namespace MQTTnet.Server.Internal.Adapter +namespace MQTTnet.Server.Internal.Adapter; + +public sealed class MqttTcpServerListener : IDisposable { - public sealed class MqttTcpServerListener : IDisposable + readonly MqttNetSourceLogger _logger; + readonly IMqttNetLogger _rootLogger; + readonly AddressFamily _addressFamily; + readonly MqttServerOptions _serverOptions; + readonly MqttServerTcpEndpointBaseOptions _options; + readonly MqttServerTlsTcpEndpointOptions _tlsOptions; + + CrossPlatformSocket _socket; + IPEndPoint _localEndPoint; + + public MqttTcpServerListener( + AddressFamily addressFamily, + MqttServerOptions serverOptions, + MqttServerTcpEndpointBaseOptions tcpEndpointOptions, + IMqttNetLogger logger) { - readonly MqttNetSourceLogger _logger; - readonly IMqttNetLogger _rootLogger; - readonly AddressFamily _addressFamily; - readonly MqttServerOptions _serverOptions; - readonly MqttServerTcpEndpointBaseOptions _options; - readonly MqttServerTlsTcpEndpointOptions _tlsOptions; - - CrossPlatformSocket _socket; - IPEndPoint _localEndPoint; - - public MqttTcpServerListener( - AddressFamily addressFamily, - MqttServerOptions serverOptions, - MqttServerTcpEndpointBaseOptions tcpEndpointOptions, - IMqttNetLogger logger) - { - _addressFamily = addressFamily; - _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); - _options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions)); - _rootLogger = logger; - _logger = logger.WithSource(nameof(MqttTcpServerListener)); + _addressFamily = addressFamily; + _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); + _options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions)); + _rootLogger = logger; + _logger = logger.WithSource(nameof(MqttTcpServerListener)); - if (_options is MqttServerTlsTcpEndpointOptions tlsOptions) - { - _tlsOptions = tlsOptions; - } + if (_options is MqttServerTlsTcpEndpointOptions tlsOptions) + { + _tlsOptions = tlsOptions; } + } - public Func ClientHandler { get; set; } + public Func ClientHandler { get; set; } - public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken) + public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken) + { + try { - try + var boundIp = _options.BoundInterNetworkAddress; + if (_addressFamily == AddressFamily.InterNetworkV6) { - var boundIp = _options.BoundInterNetworkAddress; - if (_addressFamily == AddressFamily.InterNetworkV6) - { - boundIp = _options.BoundInterNetworkV6Address; - } + boundIp = _options.BoundInterNetworkV6Address; + } - _localEndPoint = new IPEndPoint(boundIp, _options.Port); + _localEndPoint = new IPEndPoint(boundIp, _options.Port); - _logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null); + _logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null); - _socket = new CrossPlatformSocket(_addressFamily, ProtocolType.Tcp); + _socket = new CrossPlatformSocket(_addressFamily, ProtocolType.Tcp); - // Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2 - if (_options.ReuseAddress) - { - _socket.ReuseAddress = true; - } + // Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2 + if (_options.ReuseAddress) + { + _socket.ReuseAddress = true; + } - if (_options.NoDelay) - { - _socket.NoDelay = true; - } + if (_options.NoDelay) + { + _socket.NoDelay = true; + } - if (_options.LingerState != null) - { - _socket.LingerState = _options.LingerState; - } + if (_options.LingerState != null) + { + _socket.LingerState = _options.LingerState; + } - if (_options.KeepAlive.HasValue) - { - _socket.KeepAlive = _options.KeepAlive.Value; - } + if (_options.KeepAlive.HasValue) + { + _socket.KeepAlive = _options.KeepAlive.Value; + } - if (_options.TcpKeepAliveInterval.HasValue) - { - _socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value; - } + if (_options.TcpKeepAliveInterval.HasValue) + { + _socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value; + } - if (_options.TcpKeepAliveRetryCount.HasValue) - { - _socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value; - } + if (_options.TcpKeepAliveRetryCount.HasValue) + { + _socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value; + } - if (_options.TcpKeepAliveTime.HasValue) - { - _socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value; - } + if (_options.TcpKeepAliveTime.HasValue) + { + _socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value; + } - _socket.Bind(_localEndPoint); + _socket.Bind(_localEndPoint); - // Get the local endpoint back from the socket. The port may have changed. - // This can happen when port 0 is used. Then the OS will choose the next free port. - _localEndPoint = (IPEndPoint)_socket.LocalEndPoint; - _options.Port = _localEndPoint.Port; + // Get the local endpoint back from the socket. The port may have changed. + // This can happen when port 0 is used. Then the OS will choose the next free port. + _localEndPoint = (IPEndPoint)_socket.LocalEndPoint; + _options.Port = _localEndPoint.Port; - _socket.Listen(_options.ConnectionBacklog); + _socket.Listen(_options.ConnectionBacklog); - _logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint); + _logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint); - Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger); + Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger); - return true; - } - catch (Exception exception) + return true; + } + catch (Exception exception) + { + if (!treatErrorsAsWarning) { - if (!treatErrorsAsWarning) - { - throw; - } - - _logger.Warning(exception, "Error while starting TCP listener (Endpoint={0})", _localEndPoint); - return false; + throw; } - } - public void Dispose() - { - _socket?.Dispose(); + _logger.Warning(exception, "Error while starting TCP listener (Endpoint={0})", _localEndPoint); + return false; } + } + + public void Dispose() + { + _socket?.Dispose(); + } - async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken) + async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) { - while (!cancellationToken.IsCancellationRequested) + try { - try + var clientSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false); + if (clientSocket == null) { - var clientSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false); - if (clientSocket == null) - { - continue; - } - - _ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false); + continue; } - catch (OperationCanceledException) - { - } - catch (Exception exception) + + _ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + if (exception is SocketException socketException) { - if (exception is SocketException socketException) + if (socketException.SocketErrorCode is SocketError.ConnectionAborted or SocketError.OperationAborted) { - if (socketException.SocketErrorCode == SocketError.ConnectionAborted || - socketException.SocketErrorCode == SocketError.OperationAborted) - { - continue; - } + continue; } - - _logger.Error(exception, "Error while accepting TCP connection (Endpoint={0})", _localEndPoint); - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); } + + _logger.Error(exception, "Error while accepting TCP connection (Endpoint={0})", _localEndPoint); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); } } + } - async Task TryHandleClientConnectionAsync(CrossPlatformSocket clientSocket) - { - Stream stream = null; - EndPoint remoteEndPoint = null; + async Task TryHandleClientConnectionAsync(CrossPlatformSocket clientSocket) + { + Stream stream = null; + EndPoint remoteEndPoint = null; - try - { - remoteEndPoint = clientSocket.RemoteEndPoint; + try + { + remoteEndPoint = clientSocket.RemoteEndPoint; - _logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint); + _logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint); - clientSocket.NoDelay = _options.NoDelay; - stream = clientSocket.GetStream(); - var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate(); + clientSocket.NoDelay = _options.NoDelay; + stream = clientSocket.GetStream(); + var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate(); - if (clientCertificate != null) + if (clientCertificate != null) + { + if (!clientCertificate.HasPrivateKey) { - if (!clientCertificate.HasPrivateKey) - { - throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); - } + throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); + } - var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback); + var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback); - await sslStream.AuthenticateAsServerAsync( - new SslServerAuthenticationOptions - { - ServerCertificate = clientCertificate, - ClientCertificateRequired = _tlsOptions.ClientCertificateRequired, - EnabledSslProtocols = _tlsOptions.SslProtocol, - CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, - EncryptionPolicy = EncryptionPolicy.RequireEncryption, - CipherSuitesPolicy = _tlsOptions.CipherSuitesPolicy - }).ConfigureAwait(false); + await sslStream.AuthenticateAsServerAsync( + new SslServerAuthenticationOptions + { + ServerCertificate = clientCertificate, + ClientCertificateRequired = _tlsOptions.ClientCertificateRequired, + EnabledSslProtocols = _tlsOptions.SslProtocol, + CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, + EncryptionPolicy = EncryptionPolicy.RequireEncryption, + CipherSuitesPolicy = _tlsOptions.CipherSuitesPolicy + }).ConfigureAwait(false); - stream = sslStream; + stream = sslStream; - clientCertificate = sslStream.RemoteCertificate as X509Certificate2; + clientCertificate = sslStream.RemoteCertificate as X509Certificate2; - if (clientCertificate == null && sslStream.RemoteCertificate != null) - { - clientCertificate = new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert)); - } + if (clientCertificate == null && sslStream.RemoteCertificate != null) + { + clientCertificate = new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert)); } + } - var clientHandler = ClientHandler; - if (clientHandler != null) - { - var tcpChannel = new MqttTcpChannel(stream, remoteEndPoint, clientCertificate); - var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax); - var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter); + var clientHandler = ClientHandler; + if (clientHandler != null) + { + var tcpChannel = new MqttTcpChannel(stream, remoteEndPoint, clientCertificate); + var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax); + var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter); - using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger)) - { - clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation; - await clientHandler(clientAdapter).ConfigureAwait(false); - } - } + using var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger); + clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation; + await clientHandler(clientAdapter).ConfigureAwait(false); } - catch (Exception exception) + } + catch (Exception exception) + { + if (exception is ObjectDisposedException) { - if (exception is ObjectDisposedException) - { - // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. - return; - } + // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. + return; + } - if (exception is SocketException socketException && - socketException.SocketErrorCode == SocketError.OperationAborted) + if (exception is SocketException socketException && + socketException.SocketErrorCode == SocketError.OperationAborted) + { + return; + } + + _logger.Error(exception, "Error while handling TCP client connection"); + } + finally + { + try + { + if (stream != null) { - return; + await stream.DisposeAsync().ConfigureAwait(false); } - _logger.Error(exception, "Error while handling TCP client connection"); + clientSocket?.Dispose(); } - finally + catch (Exception disposeException) { - try - { - // ReSharper disable once MethodHasAsyncOverload - stream?.Dispose(); - clientSocket?.Dispose(); - } - catch (Exception disposeException) - { - _logger.Error(disposeException, "Error while cleaning up client connection"); - } + _logger.Error(disposeException, "Error while cleaning up client connection"); } - - _logger.Verbose("TCP client '{0}' disconnected (Local endpoint={1})", remoteEndPoint, _localEndPoint); } + + _logger.Verbose("TCP client '{0}' disconnected (Local endpoint={1})", remoteEndPoint, _localEndPoint); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs b/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs index 298a6b21d..1e1eefd54 100644 --- a/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs +++ b/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs @@ -4,18 +4,17 @@ using MQTTnet.Protocol; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class CheckSubscriptionsResult { - public sealed class CheckSubscriptionsResult - { - public static CheckSubscriptionsResult NotSubscribed { get; } = new CheckSubscriptionsResult(); + public static CheckSubscriptionsResult NotSubscribed { get; } = new CheckSubscriptionsResult(); + + public bool IsSubscribed { get; set; } + + public bool RetainAsPublished { get; set; } - public bool IsSubscribed { get; set; } + public List SubscriptionIdentifiers { get; set; } - public bool RetainAsPublished { get; set; } - - public List SubscriptionIdentifiers { get; set; } - - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } - } -} + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs b/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs index e7daeecc8..190b48bc8 100644 --- a/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs +++ b/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs @@ -4,24 +4,23 @@ using MQTTnet.Packets; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class DispatchApplicationMessageResult { - public sealed class DispatchApplicationMessageResult + public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List userProperties) { - public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List userProperties) - { - ReasonCode = reasonCode; - CloseConnection = closeConnection; - ReasonString = reasonString; - UserProperties = userProperties; - } + ReasonCode = reasonCode; + CloseConnection = closeConnection; + ReasonString = reasonString; + UserProperties = userProperties; + } - public bool CloseConnection { get; } + public bool CloseConnection { get; } - public int ReasonCode { get; } + public int ReasonCode { get; } - public string ReasonString { get; } + public string ReasonString { get; } - public List UserProperties { get; } - } + public List UserProperties { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs b/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs index 0952efbb5..68dcaffee 100644 --- a/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs +++ b/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public enum EnqueueDataPacketResult { - public enum EnqueueDataPacketResult - { - Enqueued, - Dropped - } + Enqueued, + Dropped } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs index cd9e36760..57e190531 100644 --- a/Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs @@ -4,24 +4,23 @@ using MQTTnet.Packets; -namespace MQTTnet.Server.Internal.Formatter +namespace MQTTnet.Server.Internal.Formatter; + +public static class MqttUnsubAckPacketFactory { - public static class MqttUnsubAckPacketFactory + public static MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, UnsubscribeResult unsubscribeResult) { - public static MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, UnsubscribeResult unsubscribeResult) - { - ArgumentNullException.ThrowIfNull(unsubscribePacket); - ArgumentNullException.ThrowIfNull(unsubscribeResult); + ArgumentNullException.ThrowIfNull(unsubscribePacket); + ArgumentNullException.ThrowIfNull(unsubscribeResult); - var unsubAckPacket = new MqttUnsubAckPacket - { - PacketIdentifier = unsubscribePacket.PacketIdentifier - }; + var unsubAckPacket = new MqttUnsubAckPacket + { + PacketIdentifier = unsubscribePacket.PacketIdentifier + }; - // MQTTv5.0.0 only. - unsubAckPacket.ReasonCodes = unsubscribeResult.ReasonCodes; + // MQTTv5.0.0 only. + unsubAckPacket.ReasonCodes = unsubscribeResult.ReasonCodes; - return unsubAckPacket; - } + return unsubAckPacket; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs b/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs index af9538df6..6447d51be 100644 --- a/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs +++ b/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs @@ -1,9 +1,8 @@ -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public interface ISubscriptionChangedNotification { - public interface ISubscriptionChangedNotification - { - void OnSubscriptionsAdded(MqttSession clientSession, List subscriptionsTopics); + void OnSubscriptionsAdded(MqttSession clientSession, List subscriptionsTopics); - void OnSubscriptionsRemoved(MqttSession clientSession, List subscriptionTopics); - } + void OnSubscriptionsRemoved(MqttSession clientSession, List subscriptionTopics); } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs b/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs index 2356b554b..4b874546e 100644 --- a/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs @@ -439,10 +439,8 @@ public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter } } - using (var timeout = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) - { - await channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(_options.DefaultCommunicationTimeout); + await channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); } } @@ -673,14 +671,12 @@ async Task ReceiveConnectPacket(IMqttChannelAdapter channelAd { try { - using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) - using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken)) + using var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout); + using var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken); + var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false); + if (firstPacket is MqttConnectPacket connectPacket) { - var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false); - if (firstPacket is MqttConnectPacket connectPacket) - { - return connectPacket; - } + return connectPacket; } } catch (OperationCanceledException) diff --git a/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs b/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs index 481f2945e..63524bac5 100644 --- a/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs +++ b/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs @@ -4,101 +4,100 @@ using MQTTnet.Packets; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttClientStatistics { - public sealed class MqttClientStatistics + Statistics _statistics = new Statistics // mutable struct, don't make readonly! { - Statistics _statistics = new Statistics // mutable struct, don't make readonly! - { - // Start with 1 because the CONNACK packet is not counted here. - _receivedPacketsCount = 1, + // Start with 1 because the CONNACK packet is not counted here. + _receivedPacketsCount = 1, - // Start with 1 because the CONNECT packet is not counted here. - _sentPacketsCount = 1 - }; + // Start with 1 because the CONNECT packet is not counted here. + _sentPacketsCount = 1 + }; - public MqttClientStatistics() - { - ConnectedTimestamp = DateTime.UtcNow; - - LastPacketReceivedTimestamp = ConnectedTimestamp; - LastPacketSentTimestamp = ConnectedTimestamp; + public MqttClientStatistics() + { + ConnectedTimestamp = DateTime.UtcNow; - LastNonKeepAlivePacketReceivedTimestamp = ConnectedTimestamp; - } + LastPacketReceivedTimestamp = ConnectedTimestamp; + LastPacketSentTimestamp = ConnectedTimestamp; - public DateTime ConnectedTimestamp { get; } - public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; } + LastNonKeepAlivePacketReceivedTimestamp = ConnectedTimestamp; + } - /// - /// Timestamp of the last package that has been sent to the client ("received" from the client's perspective) - /// - public DateTime LastPacketReceivedTimestamp { get; private set; } + public DateTime ConnectedTimestamp { get; } + public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; } - /// - /// Timestamp of the last package that has been received from the client ("sent" from the client's perspective) - /// - public DateTime LastPacketSentTimestamp { get; private set; } + /// + /// Timestamp of the last package that has been sent to the client ("received" from the client's perspective) + /// + public DateTime LastPacketReceivedTimestamp { get; private set; } - public long SentApplicationMessagesCount => Volatile.Read(ref _statistics._sentApplicationMessagesCount); + /// + /// Timestamp of the last package that has been received from the client ("sent" from the client's perspective) + /// + public DateTime LastPacketSentTimestamp { get; private set; } - public long ReceivedApplicationMessagesCount => Volatile.Read(ref _statistics._receivedApplicationMessagesCount); + public long SentApplicationMessagesCount => Volatile.Read(ref _statistics._sentApplicationMessagesCount); - public long SentPacketsCount => Volatile.Read(ref _statistics._sentPacketsCount); + public long ReceivedApplicationMessagesCount => Volatile.Read(ref _statistics._receivedApplicationMessagesCount); - public long ReceivedPacketsCount => Volatile.Read(ref _statistics._receivedPacketsCount); + public long SentPacketsCount => Volatile.Read(ref _statistics._sentPacketsCount); - public void HandleReceivedPacket(MqttPacket packet) - { - ArgumentNullException.ThrowIfNull(packet); + public long ReceivedPacketsCount => Volatile.Read(ref _statistics._receivedPacketsCount); - // This class is tracking all values from Clients perspective! - LastPacketSentTimestamp = DateTime.UtcNow; + public void HandleReceivedPacket(MqttPacket packet) + { + ArgumentNullException.ThrowIfNull(packet); - Interlocked.Increment(ref _statistics._sentPacketsCount); + // This class is tracking all values from Clients perspective! + LastPacketSentTimestamp = DateTime.UtcNow; - if (packet is MqttPublishPacket) - { - Interlocked.Increment(ref _statistics._sentApplicationMessagesCount); - } + Interlocked.Increment(ref _statistics._sentPacketsCount); - if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket)) - { - LastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp; - } + if (packet is MqttPublishPacket) + { + Interlocked.Increment(ref _statistics._sentApplicationMessagesCount); } - public void ResetStatistics() => _statistics.Reset(); - public void HandleSentPacket(MqttPacket packet) + if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket)) { - ArgumentNullException.ThrowIfNull(packet); + LastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp; + } + } + public void ResetStatistics() => _statistics.Reset(); - // This class is tracking all values from Clients perspective! - LastPacketReceivedTimestamp = DateTime.UtcNow; + public void HandleSentPacket(MqttPacket packet) + { + ArgumentNullException.ThrowIfNull(packet); - Interlocked.Increment(ref _statistics._receivedPacketsCount); + // This class is tracking all values from Clients perspective! + LastPacketReceivedTimestamp = DateTime.UtcNow; - if (packet is MqttPublishPacket) - { - Interlocked.Increment(ref _statistics._receivedApplicationMessagesCount); - } + Interlocked.Increment(ref _statistics._receivedPacketsCount); + + if (packet is MqttPublishPacket) + { + Interlocked.Increment(ref _statistics._receivedApplicationMessagesCount); } + } + + struct Statistics + { + public long _receivedPacketsCount; + public long _sentPacketsCount; + + public long _receivedApplicationMessagesCount; + public long _sentApplicationMessagesCount; - struct Statistics + public void Reset() { - public long _receivedPacketsCount; - public long _sentPacketsCount; - - public long _receivedApplicationMessagesCount; - public long _sentApplicationMessagesCount; - - public void Reset() - { - Volatile.Write(ref _receivedPacketsCount, 0); - Volatile.Write(ref _sentPacketsCount, 0); - Volatile.Write(ref _receivedApplicationMessagesCount, 0); - Volatile.Write(ref _sentApplicationMessagesCount, 0); - } + Volatile.Write(ref _receivedPacketsCount, 0); + Volatile.Write(ref _sentPacketsCount, 0); + Volatile.Write(ref _receivedApplicationMessagesCount, 0); + Volatile.Write(ref _sentApplicationMessagesCount, 0); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs b/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs index 7c1023e52..381a155eb 100644 --- a/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs @@ -5,180 +5,237 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttClientSubscriptionsManager : IDisposable { - public sealed class MqttClientSubscriptionsManager : IDisposable - { - static readonly List EmptySubscriptionIdentifiers = new List(); + static readonly List EmptySubscriptionIdentifiers = new List(); - readonly MqttServerEventContainer _eventContainer; - readonly Dictionary> _noWildcardSubscriptionsByTopicHash = new Dictionary>(); - readonly MqttRetainedMessagesManager _retainedMessagesManager; + readonly MqttServerEventContainer _eventContainer; + readonly Dictionary> _noWildcardSubscriptionsByTopicHash = new Dictionary>(); + readonly MqttRetainedMessagesManager _retainedMessagesManager; - readonly MqttSession _session; + readonly MqttSession _session; - // Callback to maintain list of subscriber clients - readonly ISubscriptionChangedNotification _subscriptionChangedNotification; + // Callback to maintain list of subscriber clients + readonly ISubscriptionChangedNotification _subscriptionChangedNotification; - // Subscriptions are stored in various dictionaries and use a "topic hash"; see the MqttSubscription object for a detailed explanation. - // The additional lock is important to coordinate complex update logic with multiple steps, checks and interceptors. - readonly Dictionary _subscriptions = new Dictionary(); + // Subscriptions are stored in various dictionaries and use a "topic hash"; see the MqttSubscription object for a detailed explanation. + // The additional lock is important to coordinate complex update logic with multiple steps, checks and interceptors. + readonly Dictionary _subscriptions = new Dictionary(); - // Use subscription lock to maintain consistency across subscriptions and topic hash dictionaries - readonly ReaderWriterLockSlim _subscriptionsLock = new ReaderWriterLockSlim(); - readonly Dictionary _wildcardSubscriptionsByTopicHash = new Dictionary(); + // Use subscription lock to maintain consistency across subscriptions and topic hash dictionaries + readonly ReaderWriterLockSlim _subscriptionsLock = new ReaderWriterLockSlim(); + readonly Dictionary _wildcardSubscriptionsByTopicHash = new Dictionary(); - public MqttClientSubscriptionsManager( - MqttSession session, - MqttServerEventContainer eventContainer, - MqttRetainedMessagesManager retainedMessagesManager, - ISubscriptionChangedNotification subscriptionChangedNotification) - { - _session = session ?? throw new ArgumentNullException(nameof(session)); - _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); - _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - _subscriptionChangedNotification = subscriptionChangedNotification; - } + public MqttClientSubscriptionsManager( + MqttSession session, + MqttServerEventContainer eventContainer, + MqttRetainedMessagesManager retainedMessagesManager, + ISubscriptionChangedNotification subscriptionChangedNotification) + { + _session = session ?? throw new ArgumentNullException(nameof(session)); + _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); + _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); + _subscriptionChangedNotification = subscriptionChangedNotification; + } - public CheckSubscriptionsResult CheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel qualityOfServiceLevel, string senderId) - { - var possibleSubscriptions = new List(); + public CheckSubscriptionsResult CheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel qualityOfServiceLevel, string senderId) + { + var possibleSubscriptions = new List(); - // Check for possible subscriptions. They might have collisions but this is fine. - _subscriptionsLock.EnterReadLock(); - try + // Check for possible subscriptions. They might have collisions but this is fine. + _subscriptionsLock.EnterReadLock(); + try + { + if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions)) { - if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions)) - { - possibleSubscriptions.AddRange(noWildcardSubscriptions); - } + possibleSubscriptions.AddRange(noWildcardSubscriptions); + } - foreach (var wcs in _wildcardSubscriptionsByTopicHash) + foreach (var wcs in _wildcardSubscriptionsByTopicHash) + { + var subscriptionHash = wcs.Key; + var subscriptionsByHashMask = wcs.Value.SubscriptionsByHashMask; + foreach (var shm in subscriptionsByHashMask) { - var subscriptionHash = wcs.Key; - var subscriptionsByHashMask = wcs.Value.SubscriptionsByHashMask; - foreach (var shm in subscriptionsByHashMask) + var subscriptionHashMask = shm.Key; + if ((topicHash & subscriptionHashMask) == subscriptionHash) { - var subscriptionHashMask = shm.Key; - if ((topicHash & subscriptionHashMask) == subscriptionHash) - { - var subscriptions = shm.Value; - possibleSubscriptions.AddRange(subscriptions); - } + var subscriptions = shm.Value; + possibleSubscriptions.AddRange(subscriptions); } } } - finally + } + finally + { + _subscriptionsLock.ExitReadLock(); + } + + // The pre check has evaluated that nothing is subscribed. + // If there were some possible candidates they get checked below + // again to avoid collisions. + if (possibleSubscriptions.Count == 0) + { + return CheckSubscriptionsResult.NotSubscribed; + } + + var senderIsReceiver = string.Equals(senderId, _session.Id); + var maxQoSLevel = -1; // Not subscribed. + + HashSet subscriptionIdentifiers = null; + var retainAsPublished = false; + + foreach (var subscription in possibleSubscriptions) + { + if (subscription.NoLocal && senderIsReceiver) { - _subscriptionsLock.ExitReadLock(); + // This is a MQTTv5 feature! + continue; } - // The pre check has evaluated that nothing is subscribed. - // If there were some possible candidates they get checked below - // again to avoid collisions. - if (possibleSubscriptions.Count == 0) + if (MqttTopicFilterComparer.Compare(topic, subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) { - return CheckSubscriptionsResult.NotSubscribed; + continue; } - var senderIsReceiver = string.Equals(senderId, _session.Id); - var maxQoSLevel = -1; // Not subscribed. + if (subscription.RetainAsPublished) + { + // This is a MQTTv5 feature! + retainAsPublished = true; + } - HashSet subscriptionIdentifiers = null; - var retainAsPublished = false; + if ((int)subscription.GrantedQualityOfServiceLevel > maxQoSLevel) + { + maxQoSLevel = (int)subscription.GrantedQualityOfServiceLevel; + } - foreach (var subscription in possibleSubscriptions) + if (subscription.Identifier > 0) { - if (subscription.NoLocal && senderIsReceiver) + if (subscriptionIdentifiers == null) { - // This is a MQTTv5 feature! - continue; + subscriptionIdentifiers = new HashSet(); } - if (MqttTopicFilterComparer.Compare(topic, subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) - { - continue; - } + subscriptionIdentifiers.Add(subscription.Identifier); + } + } - if (subscription.RetainAsPublished) - { - // This is a MQTTv5 feature! - retainAsPublished = true; - } + if (maxQoSLevel == -1) + { + return CheckSubscriptionsResult.NotSubscribed; + } - if ((int)subscription.GrantedQualityOfServiceLevel > maxQoSLevel) - { - maxQoSLevel = (int)subscription.GrantedQualityOfServiceLevel; - } + var result = new CheckSubscriptionsResult + { + IsSubscribed = true, + RetainAsPublished = retainAsPublished, + SubscriptionIdentifiers = subscriptionIdentifiers?.ToList() ?? EmptySubscriptionIdentifiers, + + // Start with the same QoS as the publisher. + QualityOfServiceLevel = qualityOfServiceLevel + }; + + // Now downgrade if required. + // + // If a subscribing Client has been granted maximum QoS 1 for a particular Topic Filter, then a QoS 0 Application Message matching the filter is delivered + // to the Client at QoS 0. This means that at most one copy of the message is received by the Client. On the other hand, a QoS 2 Message published to + // the same topic is downgraded by the Server to QoS 1 for delivery to the Client, so that Client might receive duplicate copies of the Message. + + // Subscribing to a Topic Filter at QoS 2 is equivalent to saying "I would like to receive Messages matching this filter at the QoS with which they were published". + // This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, but a subscriber is able to require that the Server + // downgrades the QoS to one more suitable for its usage. + if (maxQoSLevel < (int)qualityOfServiceLevel) + { + result.QualityOfServiceLevel = (MqttQualityOfServiceLevel)maxQoSLevel; + } - if (subscription.Identifier > 0) - { - if (subscriptionIdentifiers == null) - { - subscriptionIdentifiers = new HashSet(); - } + return result; + } - subscriptionIdentifiers.Add(subscription.Identifier); - } - } + public void Dispose() + { + _subscriptionsLock?.Dispose(); + } - if (maxQoSLevel == -1) + public async Task Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(subscribePacket); + + var retainedApplicationMessages = await _retainedMessagesManager.GetMessages().ConfigureAwait(false); + var result = new SubscribeResult(subscribePacket.TopicFilters.Count); + + var addedSubscriptions = new List(); + var finalTopicFilters = new List(); + + // The topic filters are order by its QoS so that the higher QoS will win over a + // lower one. + foreach (var topicFilterItem in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel)) + { + var interceptorEventArgs = await InterceptSubscribe(topicFilterItem, subscribePacket.UserProperties, cancellationToken).ConfigureAwait(false); + var topicFilter = interceptorEventArgs.TopicFilter; + var processSubscription = interceptorEventArgs.ProcessSubscription && interceptorEventArgs.Response.ReasonCode <= MqttSubscribeReasonCode.GrantedQoS2; + + result.UserProperties = interceptorEventArgs.UserProperties; + result.ReasonString = interceptorEventArgs.ReasonString; + result.ReasonCodes.Add(interceptorEventArgs.Response.ReasonCode); + + if (interceptorEventArgs.CloseConnection) { - return CheckSubscriptionsResult.NotSubscribed; + // When any of the interceptor calls leads to a connection close the connection + // must be closed. So do not revert to false! + result.CloseConnection = true; } - var result = new CheckSubscriptionsResult + if (!processSubscription || string.IsNullOrEmpty(topicFilter.Topic)) { - IsSubscribed = true, - RetainAsPublished = retainAsPublished, - SubscriptionIdentifiers = subscriptionIdentifiers?.ToList() ?? EmptySubscriptionIdentifiers, - - // Start with the same QoS as the publisher. - QualityOfServiceLevel = qualityOfServiceLevel - }; - - // Now downgrade if required. - // - // If a subscribing Client has been granted maximum QoS 1 for a particular Topic Filter, then a QoS 0 Application Message matching the filter is delivered - // to the Client at QoS 0. This means that at most one copy of the message is received by the Client. On the other hand, a QoS 2 Message published to - // the same topic is downgraded by the Server to QoS 1 for delivery to the Client, so that Client might receive duplicate copies of the Message. - - // Subscribing to a Topic Filter at QoS 2 is equivalent to saying "I would like to receive Messages matching this filter at the QoS with which they were published". - // This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, but a subscriber is able to require that the Server - // downgrades the QoS to one more suitable for its usage. - if (maxQoSLevel < (int)qualityOfServiceLevel) - { - result.QualityOfServiceLevel = (MqttQualityOfServiceLevel)maxQoSLevel; + continue; } - return result; + var createSubscriptionResult = CreateSubscription(topicFilter, subscribePacket.SubscriptionIdentifier, interceptorEventArgs.Response.ReasonCode); + + addedSubscriptions.Add(topicFilter.Topic); + finalTopicFilters.Add(topicFilter); + + FilterRetainedApplicationMessages(retainedApplicationMessages, createSubscriptionResult, result); } - public void Dispose() + // This call will add the new subscription to the internal storage. + // So the event _ClientSubscribedTopicEvent_ must be called afterwards. + _subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions); + + if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers) { - _subscriptionsLock?.Dispose(); + foreach (var finalTopicFilter in finalTopicFilters) + { + var eventArgs = new ClientSubscribedTopicEventArgs(_session.Id, _session.UserName, finalTopicFilter, _session.Items); + await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + } } - public async Task Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(subscribePacket); + return result; + } + + public async Task Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(unsubscribePacket); - var retainedApplicationMessages = await _retainedMessagesManager.GetMessages().ConfigureAwait(false); - var result = new SubscribeResult(subscribePacket.TopicFilters.Count); + var result = new UnsubscribeResult(); - var addedSubscriptions = new List(); - var finalTopicFilters = new List(); + var removedSubscriptions = new List(); - // The topic filters are order by its QoS so that the higher QoS will win over a - // lower one. - foreach (var topicFilterItem in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel)) + _subscriptionsLock.EnterWriteLock(); + try + { + foreach (var topicFilter in unsubscribePacket.TopicFilters) { - var interceptorEventArgs = await InterceptSubscribe(topicFilterItem, subscribePacket.UserProperties, cancellationToken).ConfigureAwait(false); - var topicFilter = interceptorEventArgs.TopicFilter; - var processSubscription = interceptorEventArgs.ProcessSubscription && interceptorEventArgs.Response.ReasonCode <= MqttSubscribeReasonCode.GrantedQoS2; + _subscriptions.TryGetValue(topicFilter, out var existingSubscription); + + var interceptorEventArgs = await InterceptUnsubscribe(topicFilter, existingSubscription, unsubscribePacket.UserProperties, cancellationToken).ConfigureAwait(false); + var acceptUnsubscription = interceptorEventArgs.Response.ReasonCode == MqttUnsubscribeReasonCode.Success; result.UserProperties = interceptorEventArgs.UserProperties; - result.ReasonString = interceptorEventArgs.ReasonString; result.ReasonCodes.Add(interceptorEventArgs.Response.ReasonCode); if (interceptorEventArgs.CloseConnection) @@ -188,329 +245,271 @@ public async Task Subscribe(MqttSubscribePacket subscribePacket result.CloseConnection = true; } - if (!processSubscription || string.IsNullOrEmpty(topicFilter.Topic)) + if (!acceptUnsubscription) { continue; } - var createSubscriptionResult = CreateSubscription(topicFilter, subscribePacket.SubscriptionIdentifier, interceptorEventArgs.Response.ReasonCode); - - addedSubscriptions.Add(topicFilter.Topic); - finalTopicFilters.Add(topicFilter); - - FilterRetainedApplicationMessages(retainedApplicationMessages, createSubscriptionResult, result); - } - - // This call will add the new subscription to the internal storage. - // So the event _ClientSubscribedTopicEvent_ must be called afterwards. - _subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions); - - if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers) - { - foreach (var finalTopicFilter in finalTopicFilters) - { - var eventArgs = new ClientSubscribedTopicEventArgs(_session.Id, _session.UserName, finalTopicFilter, _session.Items); - await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - } - } - - return result; - } - - public async Task Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(unsubscribePacket); - - var result = new UnsubscribeResult(); - - var removedSubscriptions = new List(); - - _subscriptionsLock.EnterWriteLock(); - try - { - foreach (var topicFilter in unsubscribePacket.TopicFilters) + if (interceptorEventArgs.ProcessUnsubscription) { - _subscriptions.TryGetValue(topicFilter, out var existingSubscription); - - var interceptorEventArgs = await InterceptUnsubscribe(topicFilter, existingSubscription, unsubscribePacket.UserProperties, cancellationToken).ConfigureAwait(false); - var acceptUnsubscription = interceptorEventArgs.Response.ReasonCode == MqttUnsubscribeReasonCode.Success; - - result.UserProperties = interceptorEventArgs.UserProperties; - result.ReasonCodes.Add(interceptorEventArgs.Response.ReasonCode); - - if (interceptorEventArgs.CloseConnection) - { - // When any of the interceptor calls leads to a connection close the connection - // must be closed. So do not revert to false! - result.CloseConnection = true; - } + _subscriptions.Remove(topicFilter); - if (!acceptUnsubscription) + // must remove subscription object from topic hash dictionary also + if (existingSubscription != null) { - continue; - } + var topicHash = existingSubscription.TopicHash; - if (interceptorEventArgs.ProcessUnsubscription) - { - _subscriptions.Remove(topicFilter); - - // must remove subscription object from topic hash dictionary also - if (existingSubscription != null) + if (existingSubscription.TopicHasWildcard) { - var topicHash = existingSubscription.TopicHash; - - if (existingSubscription.TopicHasWildcard) + if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) { - if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) + subscriptions.RemoveSubscription(existingSubscription); + if (subscriptions.SubscriptionsByHashMask.Count == 0) { - subscriptions.RemoveSubscription(existingSubscription); - if (subscriptions.SubscriptionsByHashMask.Count == 0) - { - _wildcardSubscriptionsByTopicHash.Remove(topicHash); - } + _wildcardSubscriptionsByTopicHash.Remove(topicHash); } } - else + } + else + { + if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) { - if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) + subscriptions.Remove(existingSubscription); + if (subscriptions.Count == 0) { - subscriptions.Remove(existingSubscription); - if (subscriptions.Count == 0) - { - _noWildcardSubscriptionsByTopicHash.Remove(topicHash); - } + _noWildcardSubscriptionsByTopicHash.Remove(topicHash); } } } - - removedSubscriptions.Add(topicFilter); } - } - } - finally - { - _subscriptionsLock.ExitWriteLock(); - _subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions); - } - if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers) - { - foreach (var topicFilter in unsubscribePacket.TopicFilters) - { - var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, _session.UserName, topicFilter, _session.Items); - await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + removedSubscriptions.Add(topicFilter); } } - - return result; } - - CreateSubscriptionResult CreateSubscription(MqttTopicFilter topicFilter, uint subscriptionIdentifier, MqttSubscribeReasonCode reasonCode) + finally { - MqttQualityOfServiceLevel grantedQualityOfServiceLevel; + _subscriptionsLock.ExitWriteLock(); + _subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions); + } - if (reasonCode == MqttSubscribeReasonCode.GrantedQoS0) - { - grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; - } - else if (reasonCode == MqttSubscribeReasonCode.GrantedQoS1) - { - grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce; - } - else if (reasonCode == MqttSubscribeReasonCode.GrantedQoS2) - { - grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce; - } - else + if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers) + { + foreach (var topicFilter in unsubscribePacket.TopicFilters) { - throw new InvalidOperationException(); + var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, _session.UserName, topicFilter, _session.Items); + await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } + } - var subscription = new MqttSubscription( - topicFilter.Topic, - topicFilter.NoLocal, - topicFilter.RetainHandling, - topicFilter.RetainAsPublished, - grantedQualityOfServiceLevel, - subscriptionIdentifier); + return result; + } - bool isNewSubscription; + CreateSubscriptionResult CreateSubscription(MqttTopicFilter topicFilter, uint subscriptionIdentifier, MqttSubscribeReasonCode reasonCode) + { + MqttQualityOfServiceLevel grantedQualityOfServiceLevel; - // Add to subscriptions and maintain topic hash dictionaries + if (reasonCode == MqttSubscribeReasonCode.GrantedQoS0) + { + grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + } + else if (reasonCode == MqttSubscribeReasonCode.GrantedQoS1) + { + grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce; + } + else if (reasonCode == MqttSubscribeReasonCode.GrantedQoS2) + { + grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce; + } + else + { + throw new InvalidOperationException(); + } - _subscriptionsLock.EnterWriteLock(); - try - { - MqttTopicHash.Calculate(topicFilter.Topic, out var topicHash, out _, out var hasWildcard); + var subscription = new MqttSubscription( + topicFilter.Topic, + topicFilter.NoLocal, + topicFilter.RetainHandling, + topicFilter.RetainAsPublished, + grantedQualityOfServiceLevel, + subscriptionIdentifier); - if (_subscriptions.TryGetValue(topicFilter.Topic, out var existingSubscription)) - { - // must remove object from topic hash dictionary first - if (hasWildcard) - { - if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) - { - subscriptions.RemoveSubscription(existingSubscription); - // no need to remove empty entry because we'll be adding subscription again below - } - } - else - { - if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) - { - subscriptions.Remove(existingSubscription); - // no need to remove empty entry because we'll be adding subscription again below - } - } - } + bool isNewSubscription; + + // Add to subscriptions and maintain topic hash dictionaries - isNewSubscription = existingSubscription == null; - _subscriptions[topicFilter.Topic] = subscription; + _subscriptionsLock.EnterWriteLock(); + try + { + MqttTopicHash.Calculate(topicFilter.Topic, out var topicHash, out _, out var hasWildcard); - // Add or re-add to topic hash dictionary + if (_subscriptions.TryGetValue(topicFilter.Topic, out var existingSubscription)) + { + // must remove object from topic hash dictionary first if (hasWildcard) { - if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) + if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) { - subscriptions = new TopicHashMaskSubscriptions(); - _wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions); + subscriptions.RemoveSubscription(existingSubscription); + // no need to remove empty entry because we'll be adding subscription again below } - - subscriptions.AddSubscription(subscription); } else { - if (!_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) + if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) { - subscriptions = new HashSet(); - _noWildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions); + subscriptions.Remove(existingSubscription); + // no need to remove empty entry because we'll be adding subscription again below } - - subscriptions.Add(subscription); } } - finally - { - _subscriptionsLock.ExitWriteLock(); - } - return new CreateSubscriptionResult - { - IsNewSubscription = isNewSubscription, - Subscription = subscription - }; - } + isNewSubscription = existingSubscription == null; + _subscriptions[topicFilter.Topic] = subscription; - static void FilterRetainedApplicationMessages( - IList retainedMessages, - CreateSubscriptionResult createSubscriptionResult, - SubscribeResult subscribeResult) - { - if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe) + // Add or re-add to topic hash dictionary + if (hasWildcard) { - // This is a MQTT V5+ feature. - return; - } - - if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !createSubscriptionResult.IsNewSubscription) - { - // This is a MQTT V5+ feature. - return; - } - - for (var index = retainedMessages.Count - 1; index >= 0; index--) - { - var retainedMessage = retainedMessages[index]; - if (retainedMessage == null) - { - continue; - } - - if (MqttTopicFilterComparer.Compare(retainedMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) + if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) { - continue; + subscriptions = new TopicHashMaskSubscriptions(); + _wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions); } - var retainedMessageMatch = new MqttRetainedMessageMatch(retainedMessage, createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel); - if (retainedMessageMatch.SubscriptionQualityOfServiceLevel > retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel) + subscriptions.AddSubscription(subscription); + } + else + { + if (!_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) { - // UPGRADING the QoS is not allowed! - // From MQTT spec: Subscribing to a Topic Filter at QoS 2 is equivalent to saying - // "I would like to receive Messages matching this filter at the QoS with which they were published". - // This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, - // but a subscriber is able to require that the Server downgrades the QoS to one more suitable for its usage. - retainedMessageMatch.SubscriptionQualityOfServiceLevel = retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel; + subscriptions = new HashSet(); + _noWildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions); } - if (subscribeResult.RetainedMessages == null) - { - subscribeResult.RetainedMessages = new List(); - } + subscriptions.Add(subscription); + } + } + finally + { + _subscriptionsLock.ExitWriteLock(); + } - subscribeResult.RetainedMessages.Add(retainedMessageMatch); + return new CreateSubscriptionResult + { + IsNewSubscription = isNewSubscription, + Subscription = subscription + }; + } - // Clear the retained message from the list because the client should receive every message only - // one time even if multiple subscriptions affect them. - retainedMessages[index] = null; - } + static void FilterRetainedApplicationMessages( + IList retainedMessages, + CreateSubscriptionResult createSubscriptionResult, + SubscribeResult subscribeResult) + { + if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe) + { + // This is a MQTT V5+ feature. + return; } - async Task InterceptSubscribe( - MqttTopicFilter topicFilter, - List userProperties, - CancellationToken cancellationToken) + if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !createSubscriptionResult.IsNewSubscription) { - var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, _session.UserName, new MqttSessionStatus(_session), topicFilter, userProperties); + // This is a MQTT V5+ feature. + return; + } - if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) - { - eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS0; - } - else if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + for (var index = retainedMessages.Count - 1; index >= 0; index--) + { + var retainedMessage = retainedMessages[index]; + if (retainedMessage == null) { - eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS1; + continue; } - else if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) + + if (MqttTopicFilterComparer.Compare(retainedMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) { - eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS2; + continue; } - if (topicFilter.Topic.StartsWith("$share/")) + var retainedMessageMatch = new MqttRetainedMessageMatch(retainedMessage, createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel); + if (retainedMessageMatch.SubscriptionQualityOfServiceLevel > retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel) { - eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.SharedSubscriptionsNotSupported; + // UPGRADING the QoS is not allowed! + // From MQTT spec: Subscribing to a Topic Filter at QoS 2 is equivalent to saying + // "I would like to receive Messages matching this filter at the QoS with which they were published". + // This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, + // but a subscriber is able to require that the Server downgrades the QoS to one more suitable for its usage. + retainedMessageMatch.SubscriptionQualityOfServiceLevel = retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel; } - else + + if (subscribeResult.RetainedMessages == null) { - await _eventContainer.InterceptingSubscriptionEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + subscribeResult.RetainedMessages = new List(); } - return eventArgs; + subscribeResult.RetainedMessages.Add(retainedMessageMatch); + + // Clear the retained message from the list because the client should receive every message only + // one time even if multiple subscriptions affect them. + retainedMessages[index] = null; } + } - async Task InterceptUnsubscribe( - string topicFilter, - MqttSubscription mqttSubscription, - List userProperties, - CancellationToken cancellationToken) - { - var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, _session.Id, _session.UserName, _session.Items, topicFilter, userProperties) - { - Response = - { - ReasonCode = mqttSubscription == null ? MqttUnsubscribeReasonCode.NoSubscriptionExisted : MqttUnsubscribeReasonCode.Success - } - }; + async Task InterceptSubscribe( + MqttTopicFilter topicFilter, + List userProperties, + CancellationToken cancellationToken) + { + var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, _session.UserName, new MqttSessionStatus(_session), topicFilter, userProperties); - await _eventContainer.InterceptingUnsubscriptionEvent.InvokeAsync(clientUnsubscribingTopicEventArgs).ConfigureAwait(false); + if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) + { + eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS0; + } + else if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) + { + eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS1; + } + else if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) + { + eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS2; + } - return clientUnsubscribingTopicEventArgs; + if (topicFilter.Topic.StartsWith("$share/")) + { + eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.SharedSubscriptionsNotSupported; } + else + { + await _eventContainer.InterceptingSubscriptionEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + } + + return eventArgs; + } - sealed class CreateSubscriptionResult + async Task InterceptUnsubscribe( + string topicFilter, + MqttSubscription mqttSubscription, + List userProperties, + CancellationToken cancellationToken) + { + var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, _session.Id, _session.UserName, _session.Items, topicFilter, userProperties) { - public bool IsNewSubscription { get; set; } + Response = + { + ReasonCode = mqttSubscription == null ? MqttUnsubscribeReasonCode.NoSubscriptionExisted : MqttUnsubscribeReasonCode.Success + } + }; - public MqttSubscription Subscription { get; set; } - } + await _eventContainer.InterceptingUnsubscriptionEvent.InvokeAsync(clientUnsubscribingTopicEventArgs).ConfigureAwait(false); + + return clientUnsubscribingTopicEventArgs; + } + + sealed class CreateSubscriptionResult + { + public bool IsNewSubscription { get; set; } + + public MqttSubscription Subscription { get; set; } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index f4bd1965e..41dacb9b7 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -2,148 +2,146 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.ObjectModel; using MQTTnet.Diagnostics.Logger; using MQTTnet.Internal; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttRetainedMessagesManager { - public sealed class MqttRetainedMessagesManager - { - readonly Dictionary _messages = new Dictionary(4096); - readonly AsyncLock _storageAccessLock = new AsyncLock(); + readonly Dictionary _messages = new Dictionary(4096); + readonly AsyncLock _storageAccessLock = new AsyncLock(); - readonly MqttServerEventContainer _eventContainer; - readonly MqttNetSourceLogger _logger; + readonly MqttServerEventContainer _eventContainer; + readonly MqttNetSourceLogger _logger; - public MqttRetainedMessagesManager(MqttServerEventContainer eventContainer, IMqttNetLogger logger) - { - _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); + public MqttRetainedMessagesManager(MqttServerEventContainer eventContainer, IMqttNetLogger logger) + { + _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); - ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(logger); - _logger = logger.WithSource(nameof(MqttRetainedMessagesManager)); - } + _logger = logger.WithSource(nameof(MqttRetainedMessagesManager)); + } - public async Task Start() + public async Task Start() + { + try { - try + var eventArgs = new LoadingRetainedMessagesEventArgs(); + await _eventContainer.LoadingRetainedMessagesEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + + lock (_messages) { - var eventArgs = new LoadingRetainedMessagesEventArgs(); - await _eventContainer.LoadingRetainedMessagesEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + _messages.Clear(); - lock (_messages) + if (eventArgs.LoadedRetainedMessages != null) { - _messages.Clear(); - - if (eventArgs.LoadedRetainedMessages != null) + foreach (var retainedMessage in eventArgs.LoadedRetainedMessages) { - foreach (var retainedMessage in eventArgs.LoadedRetainedMessages) - { - _messages[retainedMessage.Topic] = retainedMessage; - } + _messages[retainedMessage.Topic] = retainedMessage; } } } - catch (Exception exception) - { - _logger.Error(exception, "Unhandled exception while loading retained messages."); - } } + catch (Exception exception) + { + _logger.Error(exception, "Unhandled exception while loading retained messages."); + } + } - public async Task UpdateMessage(string clientId, MqttApplicationMessage applicationMessage) + public async Task UpdateMessage(string clientId, MqttApplicationMessage applicationMessage) + { + ArgumentNullException.ThrowIfNull(applicationMessage); + + try { - ArgumentNullException.ThrowIfNull(applicationMessage); + List messagesForSave = null; + var saveIsRequired = false; - try + lock (_messages) { - List messagesForSave = null; - var saveIsRequired = false; + var payload = applicationMessage.Payload; + var hasPayload = payload.Length > 0; - lock (_messages) + if (!hasPayload) { - var payload = applicationMessage.Payload; - var hasPayload = payload.Length > 0; - - if (!hasPayload) + saveIsRequired = _messages.Remove(applicationMessage.Topic); + _logger.Verbose("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); + } + else + { + if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) { - saveIsRequired = _messages.Remove(applicationMessage.Topic); - _logger.Verbose("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); + _messages[applicationMessage.Topic] = applicationMessage; + saveIsRequired = true; } else { - if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) + if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || + !MqttMemoryHelper.SequenceEqual(existingMessage.Payload, payload)) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; } - else - { - if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || - !MqttMemoryHelper.SequenceEqual(existingMessage.Payload, payload)) - { - _messages[applicationMessage.Topic] = applicationMessage; - saveIsRequired = true; - } - } - - _logger.Verbose("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); } - if (saveIsRequired) - { - messagesForSave = new List(_messages.Values); - } + _logger.Verbose("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); } if (saveIsRequired) { - using (await _storageAccessLock.EnterAsync().ConfigureAwait(false)) - { - var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave); - await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - } + messagesForSave = new List(_messages.Values); } } - catch (Exception exception) + + if (saveIsRequired) { - _logger.Error(exception, "Unhandled exception while handling retained messages."); + using (await _storageAccessLock.EnterAsync().ConfigureAwait(false)) + { + var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave); + await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + } } } + catch (Exception exception) + { + _logger.Error(exception, "Unhandled exception while handling retained messages."); + } + } - public Task> GetMessages() + public Task> GetMessages() + { + lock (_messages) { - lock (_messages) - { - var result = new List(_messages.Values); - return Task.FromResult((IList)result); - } + var result = new List(_messages.Values); + return Task.FromResult((IList)result); } + } - public Task GetMessage(string topic) + public Task GetMessage(string topic) + { + lock (_messages) { - lock (_messages) + if (_messages.TryGetValue(topic, out var message)) { - if (_messages.TryGetValue(topic, out var message)) - { - return Task.FromResult(message); - } + return Task.FromResult(message); } - - return Task.FromResult(null); } - public async Task ClearMessages() + return Task.FromResult(null); + } + + public async Task ClearMessages() + { + lock (_messages) { - lock (_messages) - { - _messages.Clear(); - } + _messages.Clear(); + } - using (await _storageAccessLock.EnterAsync().ConfigureAwait(false)) - { - await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); - } + using (await _storageAccessLock.EnterAsync().ConfigureAwait(false)) + { + await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs b/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs index 9392be6d7..12aacad18 100644 --- a/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs +++ b/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs @@ -4,52 +4,51 @@ using MQTTnet.Internal; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttServerEventContainer { - public sealed class MqttServerEventContainer - { - public AsyncEvent ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent(); + public AsyncEvent ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent(); + + public AsyncEvent ClientAcknowledgedPublishPacketEvent { get; } = new AsyncEvent(); - public AsyncEvent ClientAcknowledgedPublishPacketEvent { get; } = new AsyncEvent(); + public AsyncEvent ClientConnectedEvent { get; } = new AsyncEvent(); - public AsyncEvent ClientConnectedEvent { get; } = new AsyncEvent(); + public AsyncEvent ClientDisconnectedEvent { get; } = new AsyncEvent(); - public AsyncEvent ClientDisconnectedEvent { get; } = new AsyncEvent(); + public AsyncEvent ClientSubscribedTopicEvent { get; } = new AsyncEvent(); - public AsyncEvent ClientSubscribedTopicEvent { get; } = new AsyncEvent(); + public AsyncEvent ClientUnsubscribedTopicEvent { get; } = new AsyncEvent(); - public AsyncEvent ClientUnsubscribedTopicEvent { get; } = new AsyncEvent(); + public AsyncEvent InterceptingClientEnqueueEvent { get; } = new AsyncEvent(); - public AsyncEvent InterceptingClientEnqueueEvent { get; } = new AsyncEvent(); - - public AsyncEvent ApplicationMessageEnqueuedOrDroppedEvent { get; } = new AsyncEvent(); + public AsyncEvent ApplicationMessageEnqueuedOrDroppedEvent { get; } = new AsyncEvent(); - public AsyncEvent QueuedApplicationMessageOverwrittenEvent { get; } = new AsyncEvent(); + public AsyncEvent QueuedApplicationMessageOverwrittenEvent { get; } = new AsyncEvent(); - public AsyncEvent InterceptingInboundPacketEvent { get; } = new AsyncEvent(); + public AsyncEvent InterceptingInboundPacketEvent { get; } = new AsyncEvent(); - public AsyncEvent InterceptingOutboundPacketEvent { get; } = new AsyncEvent(); + public AsyncEvent InterceptingOutboundPacketEvent { get; } = new AsyncEvent(); - public AsyncEvent InterceptingPublishEvent { get; } = new AsyncEvent(); + public AsyncEvent InterceptingPublishEvent { get; } = new AsyncEvent(); - public AsyncEvent InterceptingSubscriptionEvent { get; } = new AsyncEvent(); + public AsyncEvent InterceptingSubscriptionEvent { get; } = new AsyncEvent(); - public AsyncEvent InterceptingUnsubscriptionEvent { get; } = new AsyncEvent(); + public AsyncEvent InterceptingUnsubscriptionEvent { get; } = new AsyncEvent(); - public AsyncEvent LoadingRetainedMessagesEvent { get; } = new AsyncEvent(); + public AsyncEvent LoadingRetainedMessagesEvent { get; } = new AsyncEvent(); - public AsyncEvent PreparingSessionEvent { get; } = new AsyncEvent(); + public AsyncEvent PreparingSessionEvent { get; } = new AsyncEvent(); - public AsyncEvent RetainedMessageChangedEvent { get; } = new AsyncEvent(); + public AsyncEvent RetainedMessageChangedEvent { get; } = new AsyncEvent(); - public AsyncEvent RetainedMessagesClearedEvent { get; } = new AsyncEvent(); + public AsyncEvent RetainedMessagesClearedEvent { get; } = new AsyncEvent(); - public AsyncEvent SessionDeletedEvent { get; } = new AsyncEvent(); + public AsyncEvent SessionDeletedEvent { get; } = new AsyncEvent(); - public AsyncEvent StartedEvent { get; } = new AsyncEvent(); + public AsyncEvent StartedEvent { get; } = new AsyncEvent(); - public AsyncEvent StoppedEvent { get; } = new AsyncEvent(); + public AsyncEvent StoppedEvent { get; } = new AsyncEvent(); - public AsyncEvent ValidatingConnectionEvent { get; } = new AsyncEvent(); - } + public AsyncEvent ValidatingConnectionEvent { get; } = new AsyncEvent(); } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs b/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs index 69802e4eb..83ed9cff1 100644 --- a/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs +++ b/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs @@ -6,130 +6,129 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttServerKeepAliveMonitor { - public sealed class MqttServerKeepAliveMonitor + static readonly MqttServerClientDisconnectOptions DefaultDisconnectOptions = new MqttServerClientDisconnectOptions { - static readonly MqttServerClientDisconnectOptions DefaultDisconnectOptions = new MqttServerClientDisconnectOptions - { - ReasonCode = MqttDisconnectReasonCode.KeepAliveTimeout, - UserProperties = null, - ReasonString = null, - ServerReference = null - }; + ReasonCode = MqttDisconnectReasonCode.KeepAliveTimeout, + UserProperties = null, + ReasonString = null, + ServerReference = null + }; - readonly MqttNetSourceLogger _logger; - readonly MqttServerOptions _options; - readonly MqttClientSessionsManager _sessionsManager; + readonly MqttNetSourceLogger _logger; + readonly MqttServerOptions _options; + readonly MqttClientSessionsManager _sessionsManager; - public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); + public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); - ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(logger); - _logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor)); - } + _logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor)); + } - public void Start(CancellationToken cancellationToken) - { - // The keep alive monitor spawns a real new thread (LongRunning) because it does not - // support async/await. Async etc. is avoided here because the thread will usually check - // the connections every few milliseconds and thus the context changes (due to async) are - // only consuming resources. Also there is just 1 thread for the entire server which is fine at all! - Task.Factory.StartNew(_ => DoWork(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning).RunInBackground(_logger); - } + public void Start(CancellationToken cancellationToken) + { + // The keep alive monitor spawns a real new thread (LongRunning) because it does not + // support async/await. Async etc. is avoided here because the thread will usually check + // the connections every few milliseconds and thus the context changes (due to async) are + // only consuming resources. Also there is just 1 thread for the entire server which is fine at all! + Task.Factory.StartNew(_ => DoWork(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning).RunInBackground(_logger); + } - void DoWork(CancellationToken cancellationToken) + void DoWork(CancellationToken cancellationToken) + { + try { - try - { - _logger.Info("Starting keep alive monitor"); + _logger.Info("Starting keep alive monitor"); - while (!cancellationToken.IsCancellationRequested) - { - TryProcessClients(); - Sleep(_options.KeepAliveOptions.MonitorInterval); - } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) + while (!cancellationToken.IsCancellationRequested) { - _logger.Error(exception, "Unhandled exception while checking keep alive timeouts"); - } - finally - { - _logger.Verbose("Stopped checking keep alive timeout"); + TryProcessClients(); + Sleep(_options.KeepAliveOptions.MonitorInterval); } } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + _logger.Error(exception, "Unhandled exception while checking keep alive timeouts"); + } + finally + { + _logger.Verbose("Stopped checking keep alive timeout"); + } + } - static void Sleep(TimeSpan timeout) + static void Sleep(TimeSpan timeout) + { + try { - try - { - Thread.Sleep(timeout); - } - catch (ThreadAbortException) - { - // The ThreadAbortException is not actively catched in this project. - // So we use a one which is similar and will be catched properly. - throw new OperationCanceledException(); - } + Thread.Sleep(timeout); } + catch (ThreadAbortException) + { + // The ThreadAbortException is not actively catched in this project. + // So we use a one which is similar and will be catched properly. + throw new OperationCanceledException(); + } + } - void TryProcessClient(MqttConnectedClient connection, DateTime now) + void TryProcessClient(MqttConnectedClient connection, DateTime now) + { + try { - try + if (!connection.IsRunning) { - if (!connection.IsRunning) - { - // The connection is already dead or just created so there is no need to check it. - return; - } - - if (connection.ConnectPacket.KeepAlivePeriod == 0) - { - // The keep alive feature is not used by the current connection. - return; - } - - // Values described here: [MQTT-3.1.2-24]. - // If the client sends 5 sec. the server will allow up to 7.5 seconds. - // If the client sends 1 sec. the server will allow up to 1.5 seconds. - var maxSecondsWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D; - - var secondsWithoutPackage = (now - connection.Statistics.LastPacketSentTimestamp).TotalSeconds; - if (secondsWithoutPackage < maxSecondsWithoutPacket) - { - // A packet was received before the timeout is affected. - return; - } - - _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal", connection.Id); - - // Execute the disconnection in background so that the keep alive monitor can continue - // with checking other connections. - // We do not need to wait for the task so no await is needed. - // Also the internal state of the connection must be swapped to "Finalizing" because the - // next iteration of the keep alive timer happens. - _ = connection.StopAsync(DefaultDisconnectOptions); + // The connection is already dead or just created so there is no need to check it. + return; } - catch (Exception exception) + + if (connection.ConnectPacket.KeepAlivePeriod == 0) { - _logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts", connection.Id); + // The keep alive feature is not used by the current connection. + return; } - } - void TryProcessClients() - { - var now = DateTime.UtcNow; - foreach (var client in _sessionsManager.GetClients()) + // Values described here: [MQTT-3.1.2-24]. + // If the client sends 5 sec. the server will allow up to 7.5 seconds. + // If the client sends 1 sec. the server will allow up to 1.5 seconds. + var maxSecondsWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D; + + var secondsWithoutPackage = (now - connection.Statistics.LastPacketSentTimestamp).TotalSeconds; + if (secondsWithoutPackage < maxSecondsWithoutPacket) { - TryProcessClient(client, now); + // A packet was received before the timeout is affected. + return; } + + _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal", connection.Id); + + // Execute the disconnection in background so that the keep alive monitor can continue + // with checking other connections. + // We do not need to wait for the task so no await is needed. + // Also the internal state of the connection must be swapped to "Finalizing" because the + // next iteration of the keep alive timer happens. + _ = connection.StopAsync(DefaultDisconnectOptions); + } + catch (Exception exception) + { + _logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts", connection.Id); + } + } + + void TryProcessClients() + { + var now = DateTime.UtcNow; + foreach (var client in _sessionsManager.GetClients()) + { + TryProcessClient(client, now); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs b/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs index 2aab0b41c..37d472967 100644 --- a/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs +++ b/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs @@ -4,78 +4,77 @@ using System.Collections.ObjectModel; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttSessionsStorage { - public sealed class MqttSessionsStorage + readonly Dictionary _sessions = new Dictionary(4096); + + public void Clear() { - readonly Dictionary _sessions = new Dictionary(4096); + // Make sure that the sessions are also getting disposed! + // This object can be reused after disposing. + Dispose(); + } - public void Clear() + public void Dispose() + { + foreach (var sessionItem in _sessions) { - // Make sure that the sessions are also getting disposed! - // This object can be reused after disposing. - Dispose(); + sessionItem.Value.Dispose(); } - public void Dispose() - { - foreach (var sessionItem in _sessions) - { - sessionItem.Value.Dispose(); - } + _sessions.Clear(); + } - _sessions.Clear(); - } + public IReadOnlyCollection ReadAllSessions() + { + return new ReadOnlyCollection(_sessions.Values.ToList()); + } + + public bool TryGetSession(string id, out MqttSession session) + { + ArgumentNullException.ThrowIfNull(id); - public IReadOnlyCollection ReadAllSessions() + if (!_sessions.TryGetValue(id, out session)) { - return new ReadOnlyCollection(_sessions.Values.ToList()); + return false; } - public bool TryGetSession(string id, out MqttSession session) + // If the Session Expiry Interval is 0xFFFFFFFF (UINT_MAX), the Session does not expire. + if (session.ExpiryInterval > 0 && session.ExpiryInterval != uint.MaxValue) { - ArgumentNullException.ThrowIfNull(id); - - if (!_sessions.TryGetValue(id, out session)) + if (session.DisconnectedTimestamp.HasValue) { - return false; - } - - // If the Session Expiry Interval is 0xFFFFFFFF (UINT_MAX), the Session does not expire. - if (session.ExpiryInterval > 0 && session.ExpiryInterval != uint.MaxValue) - { - if (session.DisconnectedTimestamp.HasValue) + if (DateTime.UtcNow > session.DisconnectedTimestamp.Value.AddSeconds(session.ExpiryInterval)) { - if (DateTime.UtcNow > session.DisconnectedTimestamp.Value.AddSeconds(session.ExpiryInterval)) - { - TryRemoveSession(id, out var oldSession); - oldSession?.Dispose(); - return false; - } + TryRemoveSession(id, out var oldSession); + oldSession?.Dispose(); + return false; } } - - return true; } - public bool TryRemoveSession(string id, out MqttSession session) - { - ArgumentNullException.ThrowIfNull(id); + return true; + } - if (_sessions.TryGetValue(id, out session)) - { - _sessions.Remove(id); - return true; - } + public bool TryRemoveSession(string id, out MqttSession session) + { + ArgumentNullException.ThrowIfNull(id); - return false; + if (_sessions.TryGetValue(id, out session)) + { + _sessions.Remove(id); + return true; } - public void UpdateSession(string id, MqttSession session) - { - ArgumentNullException.ThrowIfNull(id); + return false; + } - _sessions[id] = session; - } + public void UpdateSession(string id, MqttSession session) + { + ArgumentNullException.ThrowIfNull(id); + + _sessions[id] = session; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttSubscription.cs b/Source/MQTTnet.Server/Internal/MqttSubscription.cs index 95b18cabd..d329359d3 100644 --- a/Source/MQTTnet.Server/Internal/MqttSubscription.cs +++ b/Source/MQTTnet.Server/Internal/MqttSubscription.cs @@ -4,47 +4,46 @@ using MQTTnet.Protocol; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class MqttSubscription { - public sealed class MqttSubscription + public MqttSubscription( + string topic, + bool noLocal, + MqttRetainHandling retainHandling, + bool retainAsPublished, + MqttQualityOfServiceLevel qualityOfServiceLevel, + uint identifier) { - public MqttSubscription( - string topic, - bool noLocal, - MqttRetainHandling retainHandling, - bool retainAsPublished, - MqttQualityOfServiceLevel qualityOfServiceLevel, - uint identifier) - { - Topic = topic; - NoLocal = noLocal; - RetainHandling = retainHandling; - RetainAsPublished = retainAsPublished; - GrantedQualityOfServiceLevel = qualityOfServiceLevel; - Identifier = identifier; - - MqttTopicHash.Calculate(Topic, out var hash, out var hashMask, out var hasWildcard); - TopicHash = hash; - TopicHashMask = hashMask; - TopicHasWildcard = hasWildcard; - } + Topic = topic; + NoLocal = noLocal; + RetainHandling = retainHandling; + RetainAsPublished = retainAsPublished; + GrantedQualityOfServiceLevel = qualityOfServiceLevel; + Identifier = identifier; + + MqttTopicHash.Calculate(Topic, out var hash, out var hashMask, out var hasWildcard); + TopicHash = hash; + TopicHashMask = hashMask; + TopicHasWildcard = hasWildcard; + } - public MqttQualityOfServiceLevel GrantedQualityOfServiceLevel { get; } + public MqttQualityOfServiceLevel GrantedQualityOfServiceLevel { get; } - public uint Identifier { get; } + public uint Identifier { get; } - public bool NoLocal { get; } + public bool NoLocal { get; } - public bool RetainAsPublished { get; } + public bool RetainAsPublished { get; } - public MqttRetainHandling RetainHandling { get; } + public MqttRetainHandling RetainHandling { get; } - public string Topic { get; } + public string Topic { get; } - public ulong TopicHash { get; } + public ulong TopicHash { get; } - public ulong TopicHashMask { get; } + public ulong TopicHashMask { get; } - public bool TopicHasWildcard { get; } - } + public bool TopicHasWildcard { get; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttTopicHash.cs b/Source/MQTTnet.Server/Internal/MqttTopicHash.cs index 0e23a9d87..90bff596d 100644 --- a/Source/MQTTnet.Server/Internal/MqttTopicHash.cs +++ b/Source/MQTTnet.Server/Internal/MqttTopicHash.cs @@ -2,267 +2,266 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +/* + * The MqttSubscription object stores subscription parameters and calculates + * topic hashes. + * + * Use of Topic Hashes to improve message processing performance + * ============================================================= + * + * Motivation + * ----------- + * In a typical use case for MQTT there may be many publishers (sensors or + * other devices in the field) and few subscribers (monitoring all or many topics). + * Each publisher may have one or more topic(s) to publish and therefore both, the + * number of publishers and the number of topics may be large. + * + * Maintaining subscribers in a separate container + * ----------------------------------------------- + * Instead of placing all sessions into a single _sessions container, subscribers + * are added into another _subscriberSessions container (if a client is a + * subscriber and a publisher then the client is present in both containers). The + * cost is some additional bookkeeping work upon subscription where each client + * session needs to maintain a list of subscribed topics. + * + * When the client subscribes to the first topic, then the session manager adds + * the client to the _subscriberSessions container, and when the client + * unsubscribes from the last topic then the session manager removes the client + * from the container. Now, when an application message arrives, only the list of + * subscribers need processing instead of looping through potentially thousands of + * publishers. + * + * Improving subscriber topic lookup + * --------------------------------- + * For each subscriber, it needs to be determined whether an application message + * matches any topic the subscriber has subscribed to. There may only be few + * subscribers but there may be many subscribed topics, including wildcard topics. + * + * The implemented approach uses a topic hash and a hash mask calculated on the + * subscribed topic and the published topic (the application message topic) to + * find candidates for a match, with the existing match logic evaluating a reduced + * number of candidates. + * + * For each subscription, the topic hash and a hash mask is stored with the + * subscription, and for each application message received, the hash is calculated + * for the published topic before attempting to find matching subscriptions. The + * hash calculation itself is simple and does not have a large performance impact. + * + * We'll first explain how topic hashes and hash masks are constructed and then how + * they are used. + * + * Topic hash + * ---------- + * Topic hashes are stored as 64-bit numbers. Each byte within the 64-bit number + * relates to one MQTT topic level. A checksum is calculated for each topic level + * by iterating over the characters within the topic level (cast to byte) and the + * result is stored into the corresponding byte of the 64-bit number. If a topic + * level contains a wildcard character, then 0x00 is stored instead of the + * checksum. + * + * If there are less than 8 levels then the rest of the 64-bit number is filled + * with 0xff. If there are more than 8 levels then topics where the first 8 MQTT + * topic levels are identical will have the same hash value. + * + * This is the topic hash for the MQTT topic below: 0x655D4AF1FFFFFF + * + * client1/building1/level1/sensor1 (empty) (empty) (empty) (empty) + * \_____/ \_______/ \____/ \_____/ \_____/ \_____/ \_____/ \_____/ + * | | | | | | | | + * 0x65 0x5D 0x4A 0xF1 0xFF 0xFF 0xFF 0xFF + * + * This is the topic hash for an MQTT topic containing a wildcard: 0x655D00F1FFFFFF + * + * client1/building1/ + /sensor1 (empty) (empty) (empty) (empty) + * \_____/ \_______/ \_/ \_____/ \_____/ \_____/ \_____/ \_____/ + * | | | | | | | | + * 0x65 0x5D 0 0xF1 0xFF 0xFF 0xFF 0xFF + * + * For topics that contain the multi level wildcard # at the end, the topic hash + * is filled with 0x00: 0x65004A00000000 + * + * client1/ + /level1/ # (empty) (empty) (empty) (empty) + * \_____/ \_/ \____/ \_/ \_____/ \_____/ \_____/ \_____/ + * | | | | | | | | + * 0x65 0 0x4A 0 0 0 0 0 + * + * + * Topic hash mask + * --------------- + * The hash mask simply contains 0xFF for non-wildcard topic levels and 0x00 for + * wildcard topic levels. Here are the topic hash masks for the examples above. + * + * client1/building1/level1/sensor1 (empty) (empty) (empty) (empty) + * \_____/ \_______/ \____/ \_____/ \_____/ \_____/ \_____/ \_____/ + * | | | | | | | | + * 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + * + * client1/building1/ + /sensor1 (empty) (empty) (empty) (empty) + * \_____/ \_______/ \_/ \_____/ \_____/ \_____/ \_____/ \_____/ + * | | | | | | | | + * 0xFF 0xFF 0 0xFF 0xFF 0xFF 0xFF 0xFF + * + * client1/ + /level1/ # (empty) (empty) (empty) (empty) + * \_____/ \_/ \____/ \_/ \_____/ \_____/ \_____/ \_____/ + * | | | | | | | | + * 0xFF 0 0xFF 0 0 0 0 0 + * + * + * Topic hash and hash mask properties + * ----------------------------------- + * The following properties of topic hashes and hash masks can be exploited to + * find potentially matching subscribed topics for a given published topic. + * + * (1) If a subscribed topic does not contain wildcards then the topic hash of the + * subscribed topic must be equal to the topic hash of the published topic, + * otherwise the subscribed topic cannot be a candidate for a match. + * + * (2) If a subscribed topic contains wildcards then the hash of the published + * topic masked with the subscribed topic's hash mask must be equal to the hash of + * the subscribed topic. I.e. a subscribed topic is a candidate for a match if: + * (publishedTopicHash & subscribedTopicHashMask) == subscribedTopicHash + * + * (3) If a subscribed topic contains wildcards then any potentially matching + * published topic must have a hash value that is greater than or equal to the + * hash value of the subscribed topic (because the subscribed topic contains + * zeroes in wildcard positions). + * + * Match finding + * ------------- + * The subscription manager maintains two separate dictionaries to assist finding + * matches using topic hashes: a _noWildcardSubscriptionsByTopicHash dictionary + * containing all subscriptions that do not have wildcards, and a + * _wildcardSubscriptionsByTopicHash dictionary containing subscriptions with + * wildcards. + * + * For subscriptions without wildcards, all potential candidates for a match are + * obtained by a single look-up (exploiting point 1 above). + * + * For subscriptions with wildcards, the subscription manager loops through the + * wildcard subscriptions and selects candidates that satisfy condition + * (publishedTopicHash & subscribedTopicMask) == subscribedTopicHash (point 2). + * The loop could exit early if wildcard subscriptions were stored into a sorted + * dictionary (utilizing point 3), but, after testing, there does not seem to be + * any real benefit doing so. + * + * Other considerations + * -------------------- + * Characters in the topic string are cast to byte and any additional bytes in a + * multi-byte character are disregarded. Best guess is that this does not impact + * performance in practice. + * + * Instead of one-byte checksums per topic level, one-word checksums per topic + * level could be used. If most topics contained four levels or less then hash + * buckets would be shallower. + * + * For very large numbers of topics, performing a parallel search may help further. + * + * To also handle a larger number of subscribers, it may be beneficial to maintain + * a subscribers-by-subscription-topic dictionary. + */ +public static class MqttTopicHash { - /* - * The MqttSubscription object stores subscription parameters and calculates - * topic hashes. - * - * Use of Topic Hashes to improve message processing performance - * ============================================================= - * - * Motivation - * ----------- - * In a typical use case for MQTT there may be many publishers (sensors or - * other devices in the field) and few subscribers (monitoring all or many topics). - * Each publisher may have one or more topic(s) to publish and therefore both, the - * number of publishers and the number of topics may be large. - * - * Maintaining subscribers in a separate container - * ----------------------------------------------- - * Instead of placing all sessions into a single _sessions container, subscribers - * are added into another _subscriberSessions container (if a client is a - * subscriber and a publisher then the client is present in both containers). The - * cost is some additional bookkeeping work upon subscription where each client - * session needs to maintain a list of subscribed topics. - * - * When the client subscribes to the first topic, then the session manager adds - * the client to the _subscriberSessions container, and when the client - * unsubscribes from the last topic then the session manager removes the client - * from the container. Now, when an application message arrives, only the list of - * subscribers need processing instead of looping through potentially thousands of - * publishers. - * - * Improving subscriber topic lookup - * --------------------------------- - * For each subscriber, it needs to be determined whether an application message - * matches any topic the subscriber has subscribed to. There may only be few - * subscribers but there may be many subscribed topics, including wildcard topics. - * - * The implemented approach uses a topic hash and a hash mask calculated on the - * subscribed topic and the published topic (the application message topic) to - * find candidates for a match, with the existing match logic evaluating a reduced - * number of candidates. - * - * For each subscription, the topic hash and a hash mask is stored with the - * subscription, and for each application message received, the hash is calculated - * for the published topic before attempting to find matching subscriptions. The - * hash calculation itself is simple and does not have a large performance impact. - * - * We'll first explain how topic hashes and hash masks are constructed and then how - * they are used. - * - * Topic hash - * ---------- - * Topic hashes are stored as 64-bit numbers. Each byte within the 64-bit number - * relates to one MQTT topic level. A checksum is calculated for each topic level - * by iterating over the characters within the topic level (cast to byte) and the - * result is stored into the corresponding byte of the 64-bit number. If a topic - * level contains a wildcard character, then 0x00 is stored instead of the - * checksum. - * - * If there are less than 8 levels then the rest of the 64-bit number is filled - * with 0xff. If there are more than 8 levels then topics where the first 8 MQTT - * topic levels are identical will have the same hash value. - * - * This is the topic hash for the MQTT topic below: 0x655D4AF1FFFFFF - * - * client1/building1/level1/sensor1 (empty) (empty) (empty) (empty) - * \_____/ \_______/ \____/ \_____/ \_____/ \_____/ \_____/ \_____/ - * | | | | | | | | - * 0x65 0x5D 0x4A 0xF1 0xFF 0xFF 0xFF 0xFF - * - * This is the topic hash for an MQTT topic containing a wildcard: 0x655D00F1FFFFFF - * - * client1/building1/ + /sensor1 (empty) (empty) (empty) (empty) - * \_____/ \_______/ \_/ \_____/ \_____/ \_____/ \_____/ \_____/ - * | | | | | | | | - * 0x65 0x5D 0 0xF1 0xFF 0xFF 0xFF 0xFF - * - * For topics that contain the multi level wildcard # at the end, the topic hash - * is filled with 0x00: 0x65004A00000000 - * - * client1/ + /level1/ # (empty) (empty) (empty) (empty) - * \_____/ \_/ \____/ \_/ \_____/ \_____/ \_____/ \_____/ - * | | | | | | | | - * 0x65 0 0x4A 0 0 0 0 0 - * - * - * Topic hash mask - * --------------- - * The hash mask simply contains 0xFF for non-wildcard topic levels and 0x00 for - * wildcard topic levels. Here are the topic hash masks for the examples above. - * - * client1/building1/level1/sensor1 (empty) (empty) (empty) (empty) - * \_____/ \_______/ \____/ \_____/ \_____/ \_____/ \_____/ \_____/ - * | | | | | | | | - * 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF - * - * client1/building1/ + /sensor1 (empty) (empty) (empty) (empty) - * \_____/ \_______/ \_/ \_____/ \_____/ \_____/ \_____/ \_____/ - * | | | | | | | | - * 0xFF 0xFF 0 0xFF 0xFF 0xFF 0xFF 0xFF - * - * client1/ + /level1/ # (empty) (empty) (empty) (empty) - * \_____/ \_/ \____/ \_/ \_____/ \_____/ \_____/ \_____/ - * | | | | | | | | - * 0xFF 0 0xFF 0 0 0 0 0 - * - * - * Topic hash and hash mask properties - * ----------------------------------- - * The following properties of topic hashes and hash masks can be exploited to - * find potentially matching subscribed topics for a given published topic. - * - * (1) If a subscribed topic does not contain wildcards then the topic hash of the - * subscribed topic must be equal to the topic hash of the published topic, - * otherwise the subscribed topic cannot be a candidate for a match. - * - * (2) If a subscribed topic contains wildcards then the hash of the published - * topic masked with the subscribed topic's hash mask must be equal to the hash of - * the subscribed topic. I.e. a subscribed topic is a candidate for a match if: - * (publishedTopicHash & subscribedTopicHashMask) == subscribedTopicHash - * - * (3) If a subscribed topic contains wildcards then any potentially matching - * published topic must have a hash value that is greater than or equal to the - * hash value of the subscribed topic (because the subscribed topic contains - * zeroes in wildcard positions). - * - * Match finding - * ------------- - * The subscription manager maintains two separate dictionaries to assist finding - * matches using topic hashes: a _noWildcardSubscriptionsByTopicHash dictionary - * containing all subscriptions that do not have wildcards, and a - * _wildcardSubscriptionsByTopicHash dictionary containing subscriptions with - * wildcards. - * - * For subscriptions without wildcards, all potential candidates for a match are - * obtained by a single look-up (exploiting point 1 above). - * - * For subscriptions with wildcards, the subscription manager loops through the - * wildcard subscriptions and selects candidates that satisfy condition - * (publishedTopicHash & subscribedTopicMask) == subscribedTopicHash (point 2). - * The loop could exit early if wildcard subscriptions were stored into a sorted - * dictionary (utilizing point 3), but, after testing, there does not seem to be - * any real benefit doing so. - * - * Other considerations - * -------------------- - * Characters in the topic string are cast to byte and any additional bytes in a - * multi-byte character are disregarded. Best guess is that this does not impact - * performance in practice. - * - * Instead of one-byte checksums per topic level, one-word checksums per topic - * level could be used. If most topics contained four levels or less then hash - * buckets would be shallower. - * - * For very large numbers of topics, performing a parallel search may help further. - * - * To also handle a larger number of subscribers, it may be beneficial to maintain - * a subscribers-by-subscription-topic dictionary. - */ - public static class MqttTopicHash + public static void Calculate(string topic, out ulong resultHash, out ulong resultHashMask, out bool resultHasWildcard) { - public static void Calculate(string topic, out ulong resultHash, out ulong resultHashMask, out bool resultHasWildcard) - { - // calculate topic hash - ulong hash = 0; - ulong hashMaskInverted = 0; - ulong levelBitMask = 0; - ulong fillLevelBitMask = 0; - var hasWildcard = false; - byte checkSum = 0; - var level = 0; + // calculate topic hash + ulong hash = 0; + ulong hashMaskInverted = 0; + ulong levelBitMask = 0; + ulong fillLevelBitMask = 0; + var hasWildcard = false; + byte checkSum = 0; + var level = 0; - var i = 0; - while (i < topic.Length) + var i = 0; + while (i < topic.Length) + { + var c = topic[i]; + if (c == MqttTopicFilterComparer.LevelSeparator) { - var c = topic[i]; - if (c == MqttTopicFilterComparer.LevelSeparator) - { - // done with this level - hash <<= 8; - hash |= checkSum; - hashMaskInverted <<= 8; - hashMaskInverted |= levelBitMask; - checkSum = 0; - levelBitMask = 0; - ++level; - if (level >= 8) - { - break; - } - } - else if (c == MqttTopicFilterComparer.SingleLevelWildcard) + // done with this level + hash <<= 8; + hash |= checkSum; + hashMaskInverted <<= 8; + hashMaskInverted |= levelBitMask; + checkSum = 0; + levelBitMask = 0; + ++level; + if (level >= 8) { - levelBitMask = 0xff; - hasWildcard = true; + break; } - else if (c == MqttTopicFilterComparer.MultiLevelWildcard) + } + else if (c == MqttTopicFilterComparer.SingleLevelWildcard) + { + levelBitMask = 0xff; + hasWildcard = true; + } + else if (c == MqttTopicFilterComparer.MultiLevelWildcard) + { + // checksum is zero for a valid topic + levelBitMask = 0xff; + // fill rest with this fillLevelBitMask + fillLevelBitMask = 0xff; + hasWildcard = true; + break; + } + else + { + // The checksum should be designed to reduce the hash bucket depth for the expected + // fairly regularly named MQTT topics that don't differ much, + // i.e. "room1/sensor1" + // "room1/sensor2" + // "room1/sensor3" + // etc. + if ((c & 1) == 0) { - // checksum is zero for a valid topic - levelBitMask = 0xff; - // fill rest with this fillLevelBitMask - fillLevelBitMask = 0xff; - hasWildcard = true; - break; + checkSum += (byte)c; } else { - // The checksum should be designed to reduce the hash bucket depth for the expected - // fairly regularly named MQTT topics that don't differ much, - // i.e. "room1/sensor1" - // "room1/sensor2" - // "room1/sensor3" - // etc. - if ((c & 1) == 0) - { - checkSum += (byte)c; - } - else - { - checkSum ^= (byte)(c >> 1); - } + checkSum ^= (byte)(c >> 1); } - - ++i; } - // Shift hash left and leave zeroes to fill ulong - if (level < 8) + ++i; + } + + // Shift hash left and leave zeroes to fill ulong + if (level < 8) + { + hash <<= 8; + hash |= checkSum; + hashMaskInverted <<= 8; + hashMaskInverted |= levelBitMask; + ++level; + while (level < 8) { hash <<= 8; - hash |= checkSum; hashMaskInverted <<= 8; - hashMaskInverted |= levelBitMask; + hashMaskInverted |= fillLevelBitMask; ++level; - while (level < 8) - { - hash <<= 8; - hashMaskInverted <<= 8; - hashMaskInverted |= fillLevelBitMask; - ++level; - } } + } - if (!hasWildcard) + if (!hasWildcard) + { + while (i < topic.Length) { - while (i < topic.Length) + var c = topic[i]; + if (c == MqttTopicFilterComparer.SingleLevelWildcard || c == MqttTopicFilterComparer.MultiLevelWildcard) { - var c = topic[i]; - if (c == MqttTopicFilterComparer.SingleLevelWildcard || c == MqttTopicFilterComparer.MultiLevelWildcard) - { - hasWildcard = true; - break; - } - - ++i; + hasWildcard = true; + break; } - } - resultHash = hash; - resultHashMask = ~hashMaskInverted; - resultHasWildcard = hasWildcard; + ++i; + } } + + resultHash = hash; + resultHashMask = ~hashMaskInverted; + resultHasWildcard = hasWildcard; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/SubscribeResult.cs b/Source/MQTTnet.Server/Internal/SubscribeResult.cs index c4d20aa11..b2bd48300 100644 --- a/Source/MQTTnet.Server/Internal/SubscribeResult.cs +++ b/Source/MQTTnet.Server/Internal/SubscribeResult.cs @@ -5,23 +5,22 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class SubscribeResult { - public sealed class SubscribeResult + public SubscribeResult(int topicsCount) { - public SubscribeResult(int topicsCount) - { - ReasonCodes = new List(topicsCount); - } - - public bool CloseConnection { get; set; } - - public List ReasonCodes { get; set; } + ReasonCodes = new List(topicsCount); + } - public string ReasonString { get; set; } + public bool CloseConnection { get; set; } - public List RetainedMessages { get; set; } + public List ReasonCodes { get; set; } - public List UserProperties { get; set; } - } + public string ReasonString { get; set; } + + public List RetainedMessages { get; set; } + + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs b/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs index 38f75e50d..0d6371f7a 100644 --- a/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs +++ b/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs @@ -1,31 +1,30 @@ -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +/// +/// Helper class that stores subscriptions by their topic hash mask. +/// +public sealed class TopicHashMaskSubscriptions { - /// - /// Helper class that stores subscriptions by their topic hash mask. - /// - public sealed class TopicHashMaskSubscriptions - { - public Dictionary> SubscriptionsByHashMask { get; } = new Dictionary>(); + public Dictionary> SubscriptionsByHashMask { get; } = new Dictionary>(); - public void AddSubscription(MqttSubscription subscription) + public void AddSubscription(MqttSubscription subscription) + { + if (!SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions)) { - if (!SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions)) - { - subscriptions = new HashSet(); - SubscriptionsByHashMask.Add(subscription.TopicHashMask, subscriptions); - } - subscriptions.Add(subscription); + subscriptions = new HashSet(); + SubscriptionsByHashMask.Add(subscription.TopicHashMask, subscriptions); } + subscriptions.Add(subscription); + } - public void RemoveSubscription(MqttSubscription subscription) + public void RemoveSubscription(MqttSubscription subscription) + { + if (SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions)) { - if (SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions)) + subscriptions.Remove(subscription); + if (subscriptions.Count == 0) { - subscriptions.Remove(subscription); - if (subscriptions.Count == 0) - { - SubscriptionsByHashMask.Remove(subscription.TopicHashMask); - } + SubscriptionsByHashMask.Remove(subscription.TopicHashMask); } } } diff --git a/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs b/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs index 1c1b34ebd..f91568809 100644 --- a/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs +++ b/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs @@ -5,14 +5,13 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Internal +namespace MQTTnet.Server.Internal; + +public sealed class UnsubscribeResult { - public sealed class UnsubscribeResult - { - public List ReasonCodes { get; } = new List(128); + public List ReasonCodes { get; } = new List(128); - public bool CloseConnection { get; set; } + public bool CloseConnection { get; set; } - public List UserProperties { get; set; } - } + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttServerExtensions.cs b/Source/MQTTnet.Server/MqttServerExtensions.cs index c25c89453..747a9dfb1 100644 --- a/Source/MQTTnet.Server/MqttServerExtensions.cs +++ b/Source/MQTTnet.Server/MqttServerExtensions.cs @@ -29,9 +29,9 @@ public static Task InjectApplicationMessage( ArgumentNullException.ThrowIfNull(topic); var payloadBuffer = EmptyBuffer.Array; - if (payload is string stringPayload) + if (payload != null) { - payloadBuffer = Encoding.UTF8.GetBytes(stringPayload); + payloadBuffer = Encoding.UTF8.GetBytes(payload); } return server.InjectApplicationMessage( diff --git a/Source/MQTTnet.Server/MqttServerFactory.cs b/Source/MQTTnet.Server/MqttServerFactory.cs index 36e640599..9971c5a47 100644 --- a/Source/MQTTnet.Server/MqttServerFactory.cs +++ b/Source/MQTTnet.Server/MqttServerFactory.cs @@ -21,10 +21,7 @@ public MqttServerFactory(IMqttNetLogger logger) public IMqttNetLogger DefaultLogger { get; } - public IList> DefaultServerAdapters { get; } = new List> - { - factory => new MqttTcpServerAdapter() - }; + public IList> DefaultServerAdapters { get; } = [_ => new MqttTcpServerAdapter()]; public IDictionary Properties { get; } = new Dictionary(); diff --git a/Source/MQTTnet.Server/Options/IMqttServerCertificateCredentials.cs b/Source/MQTTnet.Server/Options/IMqttServerCertificateCredentials.cs index d0b756310..0910742dc 100644 --- a/Source/MQTTnet.Server/Options/IMqttServerCertificateCredentials.cs +++ b/Source/MQTTnet.Server/Options/IMqttServerCertificateCredentials.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public interface IMqttServerCertificateCredentials { - public interface IMqttServerCertificateCredentials - { - string Password { get; } - } -} + string Password { get; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttPendingMessagesOverflowStrategy.cs b/Source/MQTTnet.Server/Options/MqttPendingMessagesOverflowStrategy.cs index ebd4f9874..0ffdb046a 100644 --- a/Source/MQTTnet.Server/Options/MqttPendingMessagesOverflowStrategy.cs +++ b/Source/MQTTnet.Server/Options/MqttPendingMessagesOverflowStrategy.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public enum MqttPendingMessagesOverflowStrategy { - public enum MqttPendingMessagesOverflowStrategy - { - DropOldestQueuedMessage, + DropOldestQueuedMessage, - DropNewMessage - } -} + DropNewMessage +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttServerCertificateCredentials.cs b/Source/MQTTnet.Server/Options/MqttServerCertificateCredentials.cs index f0ce0013b..f5770cc90 100644 --- a/Source/MQTTnet.Server/Options/MqttServerCertificateCredentials.cs +++ b/Source/MQTTnet.Server/Options/MqttServerCertificateCredentials.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public class MqttServerCertificateCredentials : IMqttServerCertificateCredentials { - public class MqttServerCertificateCredentials : IMqttServerCertificateCredentials - { - public string Password { get; set; } - } -} + public string Password { get; set; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttServerKeepAliveOptions.cs b/Source/MQTTnet.Server/Options/MqttServerKeepAliveOptions.cs index 58f8a6739..e05e1a245 100644 --- a/Source/MQTTnet.Server/Options/MqttServerKeepAliveOptions.cs +++ b/Source/MQTTnet.Server/Options/MqttServerKeepAliveOptions.cs @@ -2,20 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class MqttServerKeepAliveOptions { - public sealed class MqttServerKeepAliveOptions - { - /// - /// When this mode is enabled the MQTT server will not close a connection when the - /// client is currently sending a (large) payload. This may lead to "dead" connections - /// When this mode is disabled the MQTT server will disconnect a client when the keep - /// alive timeout is reached even if the client is currently sending a (large) payload. - /// - public bool DisconnectClientWhenReadingPayload { get; set; } + /// + /// When this mode is enabled the MQTT server will not close a connection when the + /// client is currently sending a (large) payload. This may lead to "dead" connections + /// When this mode is disabled the MQTT server will disconnect a client when the keep + /// alive timeout is reached even if the client is currently sending a (large) payload. + /// + public bool DisconnectClientWhenReadingPayload { get; set; } - public TimeSpan MonitorInterval { get; set; } = TimeSpan.FromMilliseconds(500); - } + public TimeSpan MonitorInterval { get; set; } = TimeSpan.FromMilliseconds(500); } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttServerOptionsBuilder.cs b/Source/MQTTnet.Server/Options/MqttServerOptionsBuilder.cs index c238ea016..520358905 100644 --- a/Source/MQTTnet.Server/Options/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet.Server/Options/MqttServerOptionsBuilder.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Net; using System.Net.Security; using System.Security.Authentication; @@ -10,207 +9,206 @@ using System.Security.Cryptography.X509Certificates; // ReSharper disable UnusedMember.Global -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public class MqttServerOptionsBuilder { - public class MqttServerOptionsBuilder + readonly MqttServerOptions _options = new(); + + public MqttServerOptions Build() { - readonly MqttServerOptions _options = new MqttServerOptions(); + return _options; + } - public MqttServerOptions Build() - { - return _options; - } + public MqttServerOptionsBuilder WithClientCertificate(RemoteCertificateValidationCallback validationCallback = null, bool checkCertificateRevocation = false) + { + _options.TlsEndpointOptions.ClientCertificateRequired = true; + _options.TlsEndpointOptions.CheckCertificateRevocation = checkCertificateRevocation; + _options.TlsEndpointOptions.RemoteCertificateValidationCallback = validationCallback; + return this; + } - public MqttServerOptionsBuilder WithClientCertificate(RemoteCertificateValidationCallback validationCallback = null, bool checkCertificateRevocation = false) - { - _options.TlsEndpointOptions.ClientCertificateRequired = true; - _options.TlsEndpointOptions.CheckCertificateRevocation = checkCertificateRevocation; - _options.TlsEndpointOptions.RemoteCertificateValidationCallback = validationCallback; - return this; - } + public MqttServerOptionsBuilder WithConnectionBacklog(int value) + { + _options.DefaultEndpointOptions.ConnectionBacklog = value; + _options.TlsEndpointOptions.ConnectionBacklog = value; + return this; + } - public MqttServerOptionsBuilder WithConnectionBacklog(int value) - { - _options.DefaultEndpointOptions.ConnectionBacklog = value; - _options.TlsEndpointOptions.ConnectionBacklog = value; - return this; - } + public MqttServerOptionsBuilder WithDefaultCommunicationTimeout(TimeSpan value) + { + _options.DefaultCommunicationTimeout = value; + return this; + } - public MqttServerOptionsBuilder WithDefaultCommunicationTimeout(TimeSpan value) - { - _options.DefaultCommunicationTimeout = value; - return this; - } + public MqttServerOptionsBuilder WithDefaultEndpoint() + { + _options.DefaultEndpointOptions.IsEnabled = true; + return this; + } - public MqttServerOptionsBuilder WithDefaultEndpoint() - { - _options.DefaultEndpointOptions.IsEnabled = true; - return this; - } + public MqttServerOptionsBuilder WithDefaultEndpointBoundIPAddress(IPAddress value) + { + _options.DefaultEndpointOptions.BoundInterNetworkAddress = value ?? IPAddress.Any; + return this; + } - public MqttServerOptionsBuilder WithDefaultEndpointBoundIPAddress(IPAddress value) - { - _options.DefaultEndpointOptions.BoundInterNetworkAddress = value ?? IPAddress.Any; - return this; - } + public MqttServerOptionsBuilder WithDefaultEndpointBoundIPV6Address(IPAddress value) + { + _options.DefaultEndpointOptions.BoundInterNetworkV6Address = value ?? IPAddress.Any; + return this; + } - public MqttServerOptionsBuilder WithDefaultEndpointBoundIPV6Address(IPAddress value) - { - _options.DefaultEndpointOptions.BoundInterNetworkV6Address = value ?? IPAddress.Any; - return this; - } + public MqttServerOptionsBuilder WithDefaultEndpointPort(int value) + { + _options.DefaultEndpointOptions.Port = value; + return this; + } - public MqttServerOptionsBuilder WithDefaultEndpointPort(int value) - { - _options.DefaultEndpointOptions.Port = value; - return this; - } + public MqttServerOptionsBuilder WithDefaultEndpointReuseAddress() + { + _options.DefaultEndpointOptions.ReuseAddress = true; + return this; + } - public MqttServerOptionsBuilder WithDefaultEndpointReuseAddress() - { - _options.DefaultEndpointOptions.ReuseAddress = true; - return this; - } + public MqttServerOptionsBuilder WithEncryptedEndpoint() + { + _options.TlsEndpointOptions.IsEnabled = true; + return this; + } - public MqttServerOptionsBuilder WithEncryptedEndpoint() - { - _options.TlsEndpointOptions.IsEnabled = true; - return this; - } + public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPAddress(IPAddress value) + { + _options.TlsEndpointOptions.BoundInterNetworkAddress = value; + return this; + } - public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPAddress(IPAddress value) - { - _options.TlsEndpointOptions.BoundInterNetworkAddress = value; - return this; - } + public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPV6Address(IPAddress value) + { + _options.TlsEndpointOptions.BoundInterNetworkV6Address = value; + return this; + } - public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPV6Address(IPAddress value) - { - _options.TlsEndpointOptions.BoundInterNetworkV6Address = value; - return this; - } + public MqttServerOptionsBuilder WithEncryptedEndpointPort(int value) + { + _options.TlsEndpointOptions.Port = value; + return this; + } - public MqttServerOptionsBuilder WithEncryptedEndpointPort(int value) - { - _options.TlsEndpointOptions.Port = value; - return this; - } + public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value) + { + _options.TlsEndpointOptions.SslProtocol = value; + return this; + } - public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value) - { - _options.TlsEndpointOptions.SslProtocol = value; - return this; - } + public MqttServerOptionsBuilder WithKeepAlive() + { + _options.DefaultEndpointOptions.KeepAlive = true; + _options.TlsEndpointOptions.KeepAlive = true; + return this; + } - public MqttServerOptionsBuilder WithKeepAlive() - { - _options.DefaultEndpointOptions.KeepAlive = true; - _options.TlsEndpointOptions.KeepAlive = true; - return this; - } + public MqttServerOptionsBuilder WithMaxPendingMessagesPerClient(int value) + { + _options.MaxPendingMessagesPerClient = value; + return this; + } - public MqttServerOptionsBuilder WithMaxPendingMessagesPerClient(int value) - { - _options.MaxPendingMessagesPerClient = value; - return this; - } + public MqttServerOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value) + { + _options.PendingMessagesOverflowStrategy = value; + return this; + } - public MqttServerOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value) - { - _options.PendingMessagesOverflowStrategy = value; - return this; - } + public MqttServerOptionsBuilder WithoutDefaultEndpoint() + { + _options.DefaultEndpointOptions.IsEnabled = false; + return this; + } - public MqttServerOptionsBuilder WithoutDefaultEndpoint() - { - _options.DefaultEndpointOptions.IsEnabled = false; - return this; - } + public MqttServerOptionsBuilder WithoutEncryptedEndpoint() + { + _options.TlsEndpointOptions.IsEnabled = false; + return this; + } - public MqttServerOptionsBuilder WithoutEncryptedEndpoint() - { - _options.TlsEndpointOptions.IsEnabled = false; - return this; - } - - /// - /// Usually the MQTT packets can be send partially to improve memory allocations. - /// This is done by using multiple TCP packets or WebSocket frames etc. - /// Unfortunately not all clients do support this and will close the connection when receiving partial packets. - /// - public MqttServerOptionsBuilder WithoutPacketFragmentation() - { - _options.DefaultEndpointOptions.AllowPacketFragmentation = false; - _options.TlsEndpointOptions.AllowPacketFragmentation = false; - return this; - } + /// + /// Usually the MQTT packets can be send partially to improve memory allocations. + /// This is done by using multiple TCP packets or WebSocket frames etc. + /// Unfortunately not all clients do support this and will close the connection when receiving partial packets. + /// + public MqttServerOptionsBuilder WithoutPacketFragmentation() + { + _options.DefaultEndpointOptions.AllowPacketFragmentation = false; + _options.TlsEndpointOptions.AllowPacketFragmentation = false; + return this; + } - public MqttServerOptionsBuilder WithPersistentSessions(bool value = true) - { - _options.EnablePersistentSessions = value; - return this; - } + public MqttServerOptionsBuilder WithPersistentSessions(bool value = true) + { + _options.EnablePersistentSessions = value; + return this; + } - public MqttServerOptionsBuilder WithRemoteCertificateValidationCallback(RemoteCertificateValidationCallback value) - { - _options.TlsEndpointOptions.RemoteCertificateValidationCallback = value; - return this; - } + public MqttServerOptionsBuilder WithRemoteCertificateValidationCallback(RemoteCertificateValidationCallback value) + { + _options.TlsEndpointOptions.RemoteCertificateValidationCallback = value; + return this; + } - public MqttServerOptionsBuilder WithTcpKeepAliveInterval(int value) - { - _options.DefaultEndpointOptions.TcpKeepAliveInterval = value; - _options.TlsEndpointOptions.TcpKeepAliveInterval = value; - return this; - } + public MqttServerOptionsBuilder WithTcpKeepAliveInterval(int value) + { + _options.DefaultEndpointOptions.TcpKeepAliveInterval = value; + _options.TlsEndpointOptions.TcpKeepAliveInterval = value; + return this; + } - public MqttServerOptionsBuilder WithTcpKeepAliveRetryCount(int value) - { - _options.DefaultEndpointOptions.TcpKeepAliveRetryCount = value; - _options.TlsEndpointOptions.TcpKeepAliveRetryCount = value; - return this; - } + public MqttServerOptionsBuilder WithTcpKeepAliveRetryCount(int value) + { + _options.DefaultEndpointOptions.TcpKeepAliveRetryCount = value; + _options.TlsEndpointOptions.TcpKeepAliveRetryCount = value; + return this; + } - public MqttServerOptionsBuilder WithTcpKeepAliveTime(int value) - { - _options.DefaultEndpointOptions.TcpKeepAliveTime = value; - _options.TlsEndpointOptions.TcpKeepAliveTime = value; - return this; - } + public MqttServerOptionsBuilder WithTcpKeepAliveTime(int value) + { + _options.DefaultEndpointOptions.TcpKeepAliveTime = value; + _options.TlsEndpointOptions.TcpKeepAliveTime = value; + return this; + } - public MqttServerOptionsBuilder WithTlsEndpointReuseAddress() - { - _options.TlsEndpointOptions.ReuseAddress = true; - return this; - } + public MqttServerOptionsBuilder WithTlsEndpointReuseAddress() + { + _options.TlsEndpointOptions.ReuseAddress = true; + return this; + } - public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null) - { - ArgumentNullException.ThrowIfNull(value); + public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null) + { + ArgumentNullException.ThrowIfNull(value); - _options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value) - { - Password = credentials?.Password - }; + _options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value) + { + Password = credentials?.Password + }; - return this; - } + return this; + } - public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate) - { - ArgumentNullException.ThrowIfNull(certificate); + public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate) + { + ArgumentNullException.ThrowIfNull(certificate); - _options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate); - return this; - } + _options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate); + return this; + } - public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider) - { - ArgumentNullException.ThrowIfNull(certificateProvider); + public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider) + { + ArgumentNullException.ThrowIfNull(certificateProvider); - _options.TlsEndpointOptions.CertificateProvider = certificateProvider; + _options.TlsEndpointOptions.CertificateProvider = certificateProvider; - return this; - } + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttServerTcpEndpointBaseOptions.cs b/Source/MQTTnet.Server/Options/MqttServerTcpEndpointBaseOptions.cs index 3567050b7..bdd953151 100644 --- a/Source/MQTTnet.Server/Options/MqttServerTcpEndpointBaseOptions.cs +++ b/Source/MQTTnet.Server/Options/MqttServerTcpEndpointBaseOptions.cs @@ -5,59 +5,58 @@ using System.Net; using System.Net.Sockets; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public abstract class MqttServerTcpEndpointBaseOptions { - public abstract class MqttServerTcpEndpointBaseOptions - { - public bool IsEnabled { get; set; } - - public int Port { get; set; } - - public int ConnectionBacklog { get; set; } = 100; - - public bool NoDelay { get; set; } = true; - - /// - /// Gets or sets whether the sockets keep alive feature should be used. - /// The value _null_ indicates that the OS and framework defaults should be used. - /// - public bool? KeepAlive { get; set; } - - /// - /// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets - /// or WebSocket frames etc. Unfortunately not all clients do support this feature and - /// will close the connection when receiving such packets. If such clients are connecting to this - /// server the flag must be set to _false_. - /// - public bool AllowPacketFragmentation { get; set; } = true; - - /// - /// Gets or sets the TCP keep alive interval. - /// The value _null_ indicates that the OS and framework defaults should be used. - /// - public int? TcpKeepAliveInterval { get; set; } - - /// - /// Gets or sets the TCP keep alive retry count. - /// The value _null_ indicates that the OS and framework defaults should be used. - /// - public int? TcpKeepAliveRetryCount { get; set; } - - /// - /// Gets or sets the TCP keep alive time. - /// The value _null_ indicates that the OS and framework defaults should be used. - /// - public int? TcpKeepAliveTime { get; set; } - - public LingerOption LingerState { get; set; } = new LingerOption(true, 0); - - public IPAddress BoundInterNetworkAddress { get; set; } = IPAddress.Any; - - public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any; - - /// - /// This requires admin permissions on Linux. - /// - public bool ReuseAddress { get; set; } - } + public bool IsEnabled { get; set; } + + public int Port { get; set; } + + public int ConnectionBacklog { get; set; } = 100; + + public bool NoDelay { get; set; } = true; + + /// + /// Gets or sets whether the sockets keep alive feature should be used. + /// The value _null_ indicates that the OS and framework defaults should be used. + /// + public bool? KeepAlive { get; set; } + + /// + /// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets + /// or WebSocket frames etc. Unfortunately not all clients do support this feature and + /// will close the connection when receiving such packets. If such clients are connecting to this + /// server the flag must be set to _false_. + /// + public bool AllowPacketFragmentation { get; set; } = true; + + /// + /// Gets or sets the TCP keep alive interval. + /// The value _null_ indicates that the OS and framework defaults should be used. + /// + public int? TcpKeepAliveInterval { get; set; } + + /// + /// Gets or sets the TCP keep alive retry count. + /// The value _null_ indicates that the OS and framework defaults should be used. + /// + public int? TcpKeepAliveRetryCount { get; set; } + + /// + /// Gets or sets the TCP keep alive time. + /// The value _null_ indicates that the OS and framework defaults should be used. + /// + public int? TcpKeepAliveTime { get; set; } + + public LingerOption LingerState { get; set; } = new LingerOption(true, 0); + + public IPAddress BoundInterNetworkAddress { get; set; } = IPAddress.Any; + + public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any; + + /// + /// This requires admin permissions on Linux. + /// + public bool ReuseAddress { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttServerTcpEndpointOptions.cs b/Source/MQTTnet.Server/Options/MqttServerTcpEndpointOptions.cs index 0437da069..bc3c6744d 100644 --- a/Source/MQTTnet.Server/Options/MqttServerTcpEndpointOptions.cs +++ b/Source/MQTTnet.Server/Options/MqttServerTcpEndpointOptions.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public class MqttServerTcpEndpointOptions : MqttServerTcpEndpointBaseOptions { - public class MqttServerTcpEndpointOptions : MqttServerTcpEndpointBaseOptions + public MqttServerTcpEndpointOptions() { - public MqttServerTcpEndpointOptions() - { - Port = 1883; - } + Port = 1883; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Options/MqttServerTlsTcpEndpointOptions.cs b/Source/MQTTnet.Server/Options/MqttServerTlsTcpEndpointOptions.cs index fb8a1b02c..eb78f6f49 100644 --- a/Source/MQTTnet.Server/Options/MqttServerTlsTcpEndpointOptions.cs +++ b/Source/MQTTnet.Server/Options/MqttServerTlsTcpEndpointOptions.cs @@ -5,29 +5,28 @@ using System.Security.Authentication; using MQTTnet.Certificates; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions { - public sealed class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions + public MqttServerTlsTcpEndpointOptions() { - public MqttServerTlsTcpEndpointOptions() - { - Port = 8883; - } + Port = 8883; + } - public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; } + public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; } - public ICertificateProvider CertificateProvider { get; set; } + public ICertificateProvider CertificateProvider { get; set; } - public bool ClientCertificateRequired { get; set; } + public bool ClientCertificateRequired { get; set; } - public bool CheckCertificateRevocation { get; set; } + public bool CheckCertificateRevocation { get; set; } - /// - /// The default value is SslProtocols.None, which allows the operating system to choose the best protocol to use, and to block protocols that are not secure. - /// - /// SslProtocols - public SslProtocols SslProtocol { get; set; } = SslProtocols.None; + /// + /// The default value is SslProtocols.None, which allows the operating system to choose the best protocol to use, and to block protocols that are not secure. + /// + /// SslProtocols + public SslProtocols SslProtocol { get; set; } = SslProtocols.None; - public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; } - } -} + public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs b/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs index 5bd97b93d..1ca28a538 100644 --- a/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs +++ b/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs @@ -4,20 +4,19 @@ using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttServerStopOptions { - public sealed class MqttServerStopOptions + /// + /// These disconnect options are sent to every connected client via a DISCONNECT packet. + /// MQTT 5.0.0+ feature. + /// + public MqttServerClientDisconnectOptions DefaultClientDisconnectOptions { get; set; } = new MqttServerClientDisconnectOptions { - /// - /// These disconnect options are sent to every connected client via a DISCONNECT packet. - /// MQTT 5.0.0+ feature. - /// - public MqttServerClientDisconnectOptions DefaultClientDisconnectOptions { get; set; } = new MqttServerClientDisconnectOptions - { - ReasonCode = MqttDisconnectReasonCode.ServerShuttingDown, - UserProperties = null, - ReasonString = null, - ServerReference = null - }; - } + ReasonCode = MqttDisconnectReasonCode.ServerShuttingDown, + UserProperties = null, + ReasonString = null, + ServerReference = null + }; } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs b/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs index b851f25d7..9d3e92e87 100644 --- a/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs +++ b/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs @@ -2,34 +2,31 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +namespace MQTTnet.Server; -namespace MQTTnet.Server +public sealed class MqttServerStopOptionsBuilder { - public sealed class MqttServerStopOptionsBuilder - { - readonly MqttServerStopOptions _options = new MqttServerStopOptions(); + readonly MqttServerStopOptions _options = new(); - public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(MqttServerClientDisconnectOptions value) - { - _options.DefaultClientDisconnectOptions = value; - return this; - } + public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(MqttServerClientDisconnectOptions value) + { + _options.DefaultClientDisconnectOptions = value; + return this; + } - public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(Action builder) - { - ArgumentNullException.ThrowIfNull(builder); + public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(Action builder) + { + ArgumentNullException.ThrowIfNull(builder); - var optionsBuilder = new MqttServerClientDisconnectOptionsBuilder(); - builder.Invoke(optionsBuilder); + var optionsBuilder = new MqttServerClientDisconnectOptionsBuilder(); + builder(optionsBuilder); - _options.DefaultClientDisconnectOptions = optionsBuilder.Build(); - return this; - } + _options.DefaultClientDisconnectOptions = optionsBuilder.Build(); + return this; + } - public MqttServerStopOptions Build() - { - return _options; - } + public MqttServerStopOptions Build() + { + return _options; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/SubscribeResponse.cs b/Source/MQTTnet.Server/SubscribeResponse.cs index bbee16720..00ebf402d 100644 --- a/Source/MQTTnet.Server/SubscribeResponse.cs +++ b/Source/MQTTnet.Server/SubscribeResponse.cs @@ -18,5 +18,5 @@ public sealed class SubscribeResponse public string ReasonString { get; set; } - public List UserProperties { get; } = new(); + public List UserProperties { get; } = []; } \ No newline at end of file diff --git a/Source/MQTTnet.Server/UnsubscribeResponse.cs b/Source/MQTTnet.Server/UnsubscribeResponse.cs index 463f65ca9..741d323d5 100644 --- a/Source/MQTTnet.Server/UnsubscribeResponse.cs +++ b/Source/MQTTnet.Server/UnsubscribeResponse.cs @@ -17,5 +17,5 @@ public sealed class UnsubscribeResponse public string ReasonString { get; set; } - public List UserProperties { get; } = new(); + public List UserProperties { get; } = []; } \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/AsyncLockTest.cs b/Source/MQTTnet.TestApp/AsyncLockTest.cs index 035f3d46c..65c12826a 100644 --- a/Source/MQTTnet.TestApp/AsyncLockTest.cs +++ b/Source/MQTTnet.TestApp/AsyncLockTest.cs @@ -2,27 +2,58 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Threading; using System.Threading.Tasks; -using MQTTnet.Internal; +using MQTTnet.Exceptions; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public sealed class AsyncLockTest { - public sealed class AsyncLockTest + public async Task Run() { - public async Task Run() + try { - var asyncLock = new AsyncLock(); + var semaphore = new SemaphoreSlim(1, 1); - using (var cancellationToken = new CancellationTokenSource()) + await semaphore.WaitAsync(); + try + { + // Wait for data from socket etc... + // Then get an exception. + semaphore.Dispose(); + throw new MqttCommunicationException("Connection closed"); + } + finally { - for (var i = 0; i < 100000; i++) - { - using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false)) - { - } - } + semaphore.Release(); } } + catch (Exception exception) + { + Console.WriteLine(exception.ToString()); + } + + + + + + + + + + + + + // var asyncLock = new AsyncLock(); + // + // using var cancellationToken = new CancellationTokenSource(); + // for (var i = 0; i < 100000; i++) + // { + // using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false)) + // { + // } + // } } } \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/ClientFlowTest.cs b/Source/MQTTnet.TestApp/ClientFlowTest.cs index 9044a781e..995d6d63e 100644 --- a/Source/MQTTnet.TestApp/ClientFlowTest.cs +++ b/Source/MQTTnet.TestApp/ClientFlowTest.cs @@ -6,49 +6,48 @@ using System.Threading.Tasks; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class ClientFlowTest { - public static class ClientFlowTest + public static async Task RunAsync() { - public static async Task RunAsync() + try { - try - { - var logger = new MqttNetEventLogger(); - MqttNetConsoleLogger.ForwardToConsole(logger); + var logger = new MqttNetEventLogger(); + MqttNetConsoleLogger.ForwardToConsole(logger); - var factory = new MqttClientFactory(logger); + var factory = new MqttClientFactory(logger); - var client = factory.CreateMqttClient(); + var client = factory.CreateMqttClient(); - var options = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .Build(); + var options = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .Build(); - Console.WriteLine("BEFORE CONNECT"); - await client.ConnectAsync(options); - Console.WriteLine("AFTER CONNECT"); + Console.WriteLine("BEFORE CONNECT"); + await client.ConnectAsync(options); + Console.WriteLine("AFTER CONNECT"); - Console.WriteLine("BEFORE SUBSCRIBE"); - await client.SubscribeAsync("test/topic"); - Console.WriteLine("AFTER SUBSCRIBE"); + Console.WriteLine("BEFORE SUBSCRIBE"); + await client.SubscribeAsync("test/topic"); + Console.WriteLine("AFTER SUBSCRIBE"); - Console.WriteLine("BEFORE PUBLISH"); - await client.PublishStringAsync("test/topic", "payload"); - Console.WriteLine("AFTER PUBLISH"); + Console.WriteLine("BEFORE PUBLISH"); + await client.PublishStringAsync("test/topic", "payload"); + Console.WriteLine("AFTER PUBLISH"); - await Task.Delay(1000); + await Task.Delay(1000); - Console.WriteLine("BEFORE DISCONNECT"); - await client.DisconnectAsync(); - Console.WriteLine("AFTER DISCONNECT"); + Console.WriteLine("BEFORE DISCONNECT"); + await client.DisconnectAsync(); + Console.WriteLine("AFTER DISCONNECT"); - Console.WriteLine("FINISHED"); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } + Console.WriteLine("FINISHED"); + } + catch (Exception ex) + { + Console.WriteLine(ex); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/ClientTest.cs b/Source/MQTTnet.TestApp/ClientTest.cs index 4c68f48e2..a874da7de 100644 --- a/Source/MQTTnet.TestApp/ClientTest.cs +++ b/Source/MQTTnet.TestApp/ClientTest.cs @@ -10,99 +10,98 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class ClientTest { - public static class ClientTest + public static async Task RunAsync() { - public static async Task RunAsync() + try { - try - { - var logger = new MqttNetEventLogger(); - MqttNetConsoleLogger.ForwardToConsole(logger); + var logger = new MqttNetEventLogger(); + MqttNetConsoleLogger.ForwardToConsole(logger); - var factory = new MqttClientFactory(logger); - var client = factory.CreateMqttClient(); - var clientOptions = new MqttClientOptions + var factory = new MqttClientFactory(logger); + var client = factory.CreateMqttClient(); + var clientOptions = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) - } - }; + RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) + } + }; - client.ApplicationMessageReceivedAsync += e => - { - var payloadText = string.Empty; - if (e.ApplicationMessage.Payload.Length > 0) - { - payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); - } - - Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); - Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); - Console.WriteLine($"+ Payload = {payloadText}"); - Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); - Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); - Console.WriteLine(); - - return CompletedTask.Instance; - }; - - client.ConnectedAsync += async e => + client.ApplicationMessageReceivedAsync += e => + { + var payloadText = string.Empty; + if (e.ApplicationMessage.Payload.Length > 0) { - Console.WriteLine("### CONNECTED WITH SERVER ###"); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); + } - await client.SubscribeAsync("#"); + Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); + Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); + Console.WriteLine($"+ Payload = {payloadText}"); + Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); + Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); + Console.WriteLine(); - Console.WriteLine("### SUBSCRIBED ###"); - }; + return CompletedTask.Instance; + }; - client.DisconnectedAsync += async e => - { - Console.WriteLine("### DISCONNECTED FROM SERVER ###"); - await Task.Delay(TimeSpan.FromSeconds(5)); - - try - { - await client.ConnectAsync(clientOptions); - } - catch - { - Console.WriteLine("### RECONNECTING FAILED ###"); - } - }; + client.ConnectedAsync += async _ => + { + Console.WriteLine("### CONNECTED WITH SERVER ###"); + + await client.SubscribeAsync("#"); + + Console.WriteLine("### SUBSCRIBED ###"); + }; + + client.DisconnectedAsync += async _ => + { + Console.WriteLine("### DISCONNECTED FROM SERVER ###"); + await Task.Delay(TimeSpan.FromSeconds(5)); try { await client.ConnectAsync(clientOptions); } - catch (Exception exception) + catch { - Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); + Console.WriteLine("### RECONNECTING FAILED ###"); } + }; - Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###"); + try + { + await client.ConnectAsync(clientOptions); + } + catch (Exception exception) + { + Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); + } - while (true) - { - Console.ReadLine(); + Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###"); + + while (true) + { + Console.ReadLine(); - await client.SubscribeAsync("test"); + await client.SubscribeAsync("test"); - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("A/B/C") - .WithPayload("Hello World") - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .Build(); + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("A/B/C") + .WithPayload("Hello World") + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .Build(); - await client.PublishAsync(applicationMessage); - } - } - catch (Exception exception) - { - Console.WriteLine(exception); + await client.PublishAsync(applicationMessage); } } + catch (Exception exception) + { + Console.WriteLine(exception); + } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/MessageThroughputTest.cs b/Source/MQTTnet.TestApp/MessageThroughputTest.cs index 3d4046cba..4a813c9e9 100644 --- a/Source/MQTTnet.TestApp/MessageThroughputTest.cs +++ b/Source/MQTTnet.TestApp/MessageThroughputTest.cs @@ -1,342 +1,345 @@ using MQTTnet.Server; using System.Collections.Generic; using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Linq; using MQTTnet.Internal; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +/// +/// Connect a number of publisher clients and one subscriber client, then publish messages and measure +/// the number of messages per second that can be exchanged between publishers and subscriber. +/// Measurements are performed for subscriptions containing no wildcard, a single wildcard or multiple wildcards. +/// +public class MessageThroughputTest { - /// - /// Connect a number of publisher clients and one subscriber client, then publish messages and measure - /// the number of messages per second that can be exchanged between publishers and subscriber. - /// Measurements are performed for subscriptions containing no wildcard, a single wildcard or multiple wildcards. - /// - public class MessageThroughputTest - { - // Change these constants to suit - const int NumPublishers = 5000; - const int NumTopicsPerPublisher = 10; + // Change these constants to suit + const int NumPublishers = 5000; + const int NumTopicsPerPublisher = 10; - // Note: Other code changes may be required when changing this constant: - const int NumSubscribers = 1; // Fixed + // Note: Other code changes may be required when changing this constant: + const int NumSubscribers = 1; // Fixed - // Number of publish calls before a response for all published messages is awaited. - // This must be limited to a reasonable value that the server or TCP pipeline can handle. - const int NumPublishCallsPerBatch = 250; + // Number of publish calls before a response for all published messages is awaited. + // This must be limited to a reasonable value that the server or TCP pipeline can handle. + const int NumPublishCallsPerBatch = 250; - // Message counters are set/reset in the PublishAllAsync loop - int _messagesReceivedCount; - int _messagesExpectedCount; + // Message counters are set/reset in the PublishAllAsync loop + int _messagesReceivedCount; + int _messagesExpectedCount; - CancellationTokenSource _cancellationTokenSource; + CancellationTokenSource _cancellationTokenSource; - MqttServer _mqttServer; - Dictionary _mqttPublisherClientsByPublisherName; - List _mqttSubscriberClients; + MqttServer _mqttServer; + Dictionary _mqttPublisherClientsByPublisherName; + List _mqttSubscriberClients; - Dictionary> _topicsByPublisher; - Dictionary> _singleWildcardTopicsByPublisher; - Dictionary> _multiWildcardTopicsByPublisher; + Dictionary> _topicsByPublisher; + Dictionary> _singleWildcardTopicsByPublisher; + Dictionary> _multiWildcardTopicsByPublisher; - public async Task Run() + public async Task Run() + { + try { - try - { - Console.WriteLine(); - Console.WriteLine("Begin message throughput test"); - Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine("Begin message throughput test"); + Console.WriteLine(); - Console.WriteLine("Number of publishers: " + NumPublishers); - Console.WriteLine("Number of published topics (total): " + NumPublishers * NumTopicsPerPublisher); - Console.WriteLine("Number of subscribers: " + NumSubscribers); + Console.WriteLine("Number of publishers: " + NumPublishers); + Console.WriteLine("Number of published topics (total): " + NumPublishers * NumTopicsPerPublisher); + Console.WriteLine("Number of subscribers: " + NumSubscribers); - await Setup(); + await Setup(); - await Subscribe_to_No_Wildcard_Topics(); - await Subscribe_to_Single_Wildcard_Topics(); - await Subscribe_to_Multi_Wildcard_Topics(); + await Subscribe_to_No_Wildcard_Topics(); + await Subscribe_to_Single_Wildcard_Topics(); + await Subscribe_to_Multi_Wildcard_Topics(); - Console.WriteLine(); - Console.WriteLine("End message throughput test"); - } - catch (Exception ex) - { - ConsoleWriteLineError(ex.Message); - } - finally - { - await Cleanup(); - } + Console.WriteLine(); + Console.WriteLine("End message throughput test"); } - - public async Task Setup() + catch (Exception ex) { - new TopicGenerator().Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out _singleWildcardTopicsByPublisher, out _multiWildcardTopicsByPublisher); - - var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - var mqttClientFactory = new MqttClientFactory(); - var mqttServerFactory = new MqttServerFactory(); - _mqttServer = mqttServerFactory.CreateMqttServer(serverOptions); - await _mqttServer.StartAsync(); - - Console.WriteLine(); - Console.WriteLine("Begin connect " + NumPublishers + " publisher(s)..."); + ConsoleWriteLineError(ex.Message); + } + finally + { + await Cleanup(); + } + } - var stopWatch = new System.Diagnostics.Stopwatch(); - stopWatch.Start(); - _mqttPublisherClientsByPublisherName = new Dictionary(); - foreach (var pt in _topicsByPublisher) - { - var publisherName = pt.Key; - var mqttClient = mqttClientFactory.CreateMqttClient(); - var publisherOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .WithClientId(publisherName) - .Build(); - await mqttClient.ConnectAsync(publisherOptions); - _mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient); - } - stopWatch.Stop(); + public async Task Setup() + { + new TopicGenerator().Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out _singleWildcardTopicsByPublisher, out _multiWildcardTopicsByPublisher); - Console.Write($"{NumPublishers} publisher(s) connected in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); - Console.WriteLine($"connections per second: {NumPublishers / (stopWatch.ElapsedMilliseconds / 1000.0):0.000}"); + var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); + var mqttClientFactory = new MqttClientFactory(); + var mqttServerFactory = new MqttServerFactory(); + _mqttServer = mqttServerFactory.CreateMqttServer(serverOptions); + await _mqttServer.StartAsync(); - _mqttSubscriberClients = new List(); - for (var i = 0; i < NumSubscribers; ++i) - { - var mqttClient = mqttClientFactory.CreateMqttClient(); - var subsriberOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .WithClientId("sub" + i) - .Build(); - await mqttClient.ConnectAsync(subsriberOptions); - mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync; - _mqttSubscriberClients.Add(mqttClient); - } - } + Console.WriteLine(); + Console.WriteLine("Begin connect " + NumPublishers + " publisher(s)..."); - Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + var stopWatch = new Stopwatch(); + stopWatch.Start(); + _mqttPublisherClientsByPublisherName = []; + foreach (var pt in _topicsByPublisher) { - // count messages and signal cancellation when expected message count is reached - ++_messagesReceivedCount; - if (_messagesReceivedCount == _messagesExpectedCount) - { - _cancellationTokenSource.Cancel(); - } - return CompletedTask.Instance; + var publisherName = pt.Key; + var mqttClient = mqttClientFactory.CreateMqttClient(); + var publisherOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .WithClientId(publisherName) + .Build(); + await mqttClient.ConnectAsync(publisherOptions); + _mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient); } + stopWatch.Stop(); - public async Task Cleanup() - { - foreach (var mqttClient in _mqttSubscriberClients) - { - await mqttClient.DisconnectAsync(); - mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync; - mqttClient.Dispose(); - } + Console.Write($"{NumPublishers} publisher(s) connected in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); + Console.WriteLine($"connections per second: {NumPublishers / (stopWatch.ElapsedMilliseconds / 1000.0):0.000}"); - foreach (var pub in _mqttPublisherClientsByPublisherName) - { - var mqttClient = pub.Value; - await mqttClient.DisconnectAsync(); - mqttClient.Dispose(); - } - - await _mqttServer.StopAsync(); - _mqttServer.Dispose(); + _mqttSubscriberClients = new List(); + for (var i = 0; i < NumSubscribers; ++i) + { + var mqttClient = mqttClientFactory.CreateMqttClient(); + var subsriberOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .WithClientId("sub" + i) + .Build(); + await mqttClient.ConnectAsync(subsriberOptions); + mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync; + _mqttSubscriberClients.Add(mqttClient); } + } - /// - /// Measure no-wildcard topic subscription message exchange performance - /// - public Task Subscribe_to_No_Wildcard_Topics() + Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + { + // count messages and signal cancellation when expected message count is reached + ++_messagesReceivedCount; + if (_messagesReceivedCount == _messagesExpectedCount) { - return ProcessMessages(_topicsByPublisher, "no wildcards"); + _cancellationTokenSource.Cancel(); } + return CompletedTask.Instance; + } - /// - /// Measure single-wildcard topic subscription message exchange performance - /// - public Task Subscribe_to_Single_Wildcard_Topics() + public async Task Cleanup() + { + foreach (var mqttClient in _mqttSubscriberClients) { - return ProcessMessages(_singleWildcardTopicsByPublisher, "single wildcard"); + await mqttClient.DisconnectAsync(); + mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync; + mqttClient.Dispose(); } - /// - /// Measure multi-wildcard topic subscription message exchange performance - /// - public Task Subscribe_to_Multi_Wildcard_Topics() + foreach (var pub in _mqttPublisherClientsByPublisherName) { - return ProcessMessages(_multiWildcardTopicsByPublisher, "multi wildcard"); + var mqttClient = pub.Value; + await mqttClient.DisconnectAsync(); + mqttClient.Dispose(); } + await _mqttServer.StopAsync(); + _mqttServer.Dispose(); + } - /// - /// Subscribe to all topics, then run message exchange - /// - public async Task ProcessMessages(Dictionary> topicsByPublisher, string topicTypeDescription) - { - var numTopics = CountTopics(topicsByPublisher); + /// + /// Measure no-wildcard topic subscription message exchange performance + /// + public Task Subscribe_to_No_Wildcard_Topics() + { + return ProcessMessages(_topicsByPublisher, "no wildcards"); + } - Console.WriteLine(); - Console.Write($"Subscribing to {numTopics} topics "); - ConsoleWriteInfo($"({topicTypeDescription})"); - Console.WriteLine($" for {NumSubscribers} subscriber(s)..."); + /// + /// Measure single-wildcard topic subscription message exchange performance + /// + public Task Subscribe_to_Single_Wildcard_Topics() + { + return ProcessMessages(_singleWildcardTopicsByPublisher, "single wildcard"); + } + + /// + /// Measure multi-wildcard topic subscription message exchange performance + /// + public Task Subscribe_to_Multi_Wildcard_Topics() + { + return ProcessMessages(_multiWildcardTopicsByPublisher, "multi wildcard"); + } + + + /// + /// Subscribe to all topics, then run message exchange + /// + public async Task ProcessMessages(Dictionary> topicsByPublisher, string topicTypeDescription) + { + var numTopics = CountTopics(topicsByPublisher); + + Console.WriteLine(); + Console.Write($"Subscribing to {numTopics} topics "); + ConsoleWriteInfo($"({topicTypeDescription})"); + Console.WriteLine($" for {NumSubscribers} subscriber(s)..."); - var stopWatch = new System.Diagnostics.Stopwatch(); - stopWatch.Start(); + var stopWatch = new Stopwatch(); + stopWatch.Start(); - // each subscriber subscribes to all topics, one call per topic - foreach (var subscriber in _mqttSubscriberClients) + // each subscriber subscribes to all topics, one call per topic + foreach (var subscriber in _mqttSubscriberClients) + { + foreach (var tp in topicsByPublisher) { - foreach (var tp in topicsByPublisher) + var topics = tp.Value; + foreach (var topic in topics) { - var topics = tp.Value; - foreach (var topic in topics) - { - var topicFilter = new Packets.MqttTopicFilter() { Topic = topic }; - await subscriber.SubscribeAsync(topicFilter).ConfigureAwait(false); - } + var topicFilter = new Packets.MqttTopicFilter() { Topic = topic }; + await subscriber.SubscribeAsync(topicFilter).ConfigureAwait(false); } } + } - stopWatch.Stop(); + stopWatch.Stop(); - Console.Write($"{NumSubscribers} subscriber(s) subscribed in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); - Console.WriteLine($"subscribe calls per second: {numTopics / (stopWatch.ElapsedMilliseconds / 1000.0):0.000}"); + Console.Write($"{NumSubscribers} subscriber(s) subscribed in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); + Console.WriteLine($"subscribe calls per second: {numTopics / (stopWatch.ElapsedMilliseconds / 1000.0):0.000}"); - await PublishAllAsync(); + await PublishAllAsync(); - Console.WriteLine($"Unsubscribing {numTopics} topics ({topicTypeDescription}) for {NumSubscribers} subscriber(s)..."); + Console.WriteLine($"Unsubscribing {numTopics} topics ({topicTypeDescription}) for {NumSubscribers} subscriber(s)..."); - stopWatch.Restart(); + stopWatch.Restart(); - // unsubscribe to all topics, one call per topic - foreach (var subscriber in _mqttSubscriberClients) + // unsubscribe to all topics, one call per topic + foreach (var subscriber in _mqttSubscriberClients) + { + foreach (var tp in topicsByPublisher) { - foreach (var tp in topicsByPublisher) + var topics = tp.Value; + foreach (var topic in topics) { - var topics = tp.Value; - foreach (var topic in topics) - { - var topicFilter = new Packets.MqttTopicFilter() { Topic = topic }; + var topicFilter = new Packets.MqttTopicFilter { Topic = topic }; - MqttClientUnsubscribeOptions options = - new MqttClientUnsubscribeOptionsBuilder() + var options = + new MqttClientUnsubscribeOptionsBuilder() .WithTopicFilter(topicFilter) .Build(); - await subscriber.UnsubscribeAsync(options).ConfigureAwait(false); - } + await subscriber.UnsubscribeAsync(options).ConfigureAwait(false); } } - - stopWatch.Stop(); - - Console.Write($"{NumSubscribers} subscriber(s) unsubscribed in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); - Console.WriteLine($"unsubscribe calls per second: {numTopics / (stopWatch.ElapsedMilliseconds / 1000.0):0.000}"); } - /// - /// Publish messages in batches of NumPublishCallsPerBatch, wait for messages sent to subscriber - /// - async Task PublishAllAsync() - { - Console.WriteLine("Begin message exchange..."); - - int publisherIndexCounter = 0; // index to loop around all publishers to publish - int topicIndexCounter = 0; // index to loop around all topics to publish - int totalNumMessagesReceived = 0; + stopWatch.Stop(); - var publisherNames = _topicsByPublisher.Keys.ToList(); + Console.Write($"{NumSubscribers} subscriber(s) unsubscribed in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); + Console.WriteLine($"unsubscribe calls per second: {numTopics / (stopWatch.ElapsedMilliseconds / 1000.0):0.000}"); + } - // There should be one message received per publish - _messagesExpectedCount = NumPublishCallsPerBatch; + /// + /// Publish messages in batches of NumPublishCallsPerBatch, wait for messages sent to subscriber + /// + async Task PublishAllAsync() + { + Console.WriteLine("Begin message exchange..."); - var stopWatch = new System.Diagnostics.Stopwatch(); - stopWatch.Start(); + var publisherIndexCounter = 0; // index to loop around all publishers to publish + var topicIndexCounter = 0; // index to loop around all topics to publish + var totalNumMessagesReceived = 0; - // Loop for a while and exchange messages + var publisherNames = _topicsByPublisher.Keys.ToList(); - while (stopWatch.ElapsedMilliseconds < 10000) - { - _messagesReceivedCount = 0; + // There should be one message received per publish + _messagesExpectedCount = NumPublishCallsPerBatch; - _cancellationTokenSource = new CancellationTokenSource(); + var stopWatch = new Stopwatch(); + stopWatch.Start(); - for (var publishCallCount = 0; publishCallCount < NumPublishCallsPerBatch; ++publishCallCount) - { - // pick a publisher - var publisherName = publisherNames[publisherIndexCounter % publisherNames.Count]; - var publisherTopics = _topicsByPublisher[publisherName]; - // pick a publisher topic - var topic = publisherTopics[topicIndexCounter % publisherTopics.Count]; - var message = new MqttApplicationMessageBuilder() - .WithTopic(topic) - .Build(); - await _mqttPublisherClientsByPublisherName[publisherName].PublishAsync(message).ConfigureAwait(false); - ++topicIndexCounter; - ++publisherIndexCounter; - } + // Loop for a while and exchange messages - // Wait for at least one message per publish to be received by subscriber (in the subscriber's application message handler), - // then loop around to send another batch - try - { - await Task.Delay(30000, _cancellationTokenSource.Token).ConfigureAwait(false); - } - catch - { - } + while (stopWatch.ElapsedMilliseconds < 10000) + { + _messagesReceivedCount = 0; - _cancellationTokenSource.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); - if (_messagesReceivedCount < _messagesExpectedCount) - { - ConsoleWriteLineError(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount)); - return; - } + for (var publishCallCount = 0; publishCallCount < NumPublishCallsPerBatch; ++publishCallCount) + { + // pick a publisher + var publisherName = publisherNames[publisherIndexCounter % publisherNames.Count]; + var publisherTopics = _topicsByPublisher[publisherName]; + // pick a publisher topic + var topic = publisherTopics[topicIndexCounter % publisherTopics.Count]; + var message = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .Build(); - totalNumMessagesReceived += _messagesReceivedCount; + await _mqttPublisherClientsByPublisherName[publisherName].PublishAsync(message).ConfigureAwait(false); + ++topicIndexCounter; + ++publisherIndexCounter; } - stopWatch.Stop(); + // Wait for at least one message per publish to be received by subscriber (in the subscriber's application message handler), + // then loop around to send another batch + try + { + await Task.Delay(30000, _cancellationTokenSource.Token).ConfigureAwait(false); + } + catch (Exception exception) + { + Debug.Write(exception.ToString()); + } - Console.Write($"{totalNumMessagesReceived} messages published and received in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); - ConsoleWriteLineSuccess($"messages per second: {(int)(totalNumMessagesReceived / (stopWatch.ElapsedMilliseconds / 1000.0))}"); - } + _cancellationTokenSource.Dispose(); - int CountTopics(Dictionary> topicsByPublisher) - { - var count = 0; - foreach (var tp in topicsByPublisher) + if (_messagesReceivedCount < _messagesExpectedCount) { - count += tp.Value.Count; + ConsoleWriteLineError($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}"); + return; } - return count; - } - void ConsoleWriteLineError(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(message); - Console.ResetColor(); + totalNumMessagesReceived += _messagesReceivedCount; } - void ConsoleWriteLineSuccess(string message) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(message); - Console.ResetColor(); - } + stopWatch.Stop(); + + Console.Write($"{totalNumMessagesReceived} messages published and received in {stopWatch.ElapsedMilliseconds / 1000.0:0.000} seconds, "); + ConsoleWriteLineSuccess($"messages per second: {(int)(totalNumMessagesReceived / (stopWatch.ElapsedMilliseconds / 1000.0))}"); + } - void ConsoleWriteInfo(string message) + static int CountTopics(Dictionary> topicsByPublisher) + { + var count = 0; + foreach (var tp in topicsByPublisher) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write(message); - Console.ResetColor(); + count += tp.Value.Count; } + return count; + } + + static void ConsoleWriteLineError(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(message); + Console.ResetColor(); + } + + static void ConsoleWriteLineSuccess(string message) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(message); + Console.ResetColor(); } -} + + static void ConsoleWriteInfo(string message) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Write(message); + Console.ResetColor(); + } + +} \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/MqttNetConsoleLogger.cs b/Source/MQTTnet.TestApp/MqttNetConsoleLogger.cs index 40ea09b61..30a153e5a 100644 --- a/Source/MQTTnet.TestApp/MqttNetConsoleLogger.cs +++ b/Source/MQTTnet.TestApp/MqttNetConsoleLogger.cs @@ -6,58 +6,57 @@ using System.Text; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class MqttNetConsoleLogger { - public static class MqttNetConsoleLogger + static readonly object Lock = new(); + + public static void ForwardToConsole(MqttNetEventLogger logger) { - static readonly object _lock = new object(); + ArgumentNullException.ThrowIfNull(logger); - public static void ForwardToConsole(MqttNetEventLogger logger) - { - ArgumentNullException.ThrowIfNull(logger); + logger.LogMessagePublished -= PrintToConsole; + logger.LogMessagePublished += PrintToConsole; + } - logger.LogMessagePublished -= PrintToConsole; - logger.LogMessagePublished += PrintToConsole; + public static void PrintToConsole(string message, ConsoleColor color) + { + lock (Lock) + { + var backupColor = Console.ForegroundColor; + Console.ForegroundColor = color; + Console.Write(message); + Console.ForegroundColor = backupColor; } + } - public static void PrintToConsole(string message, ConsoleColor color) + static void PrintToConsole(object sender, MqttNetLogMessagePublishedEventArgs e) + { + var output = new StringBuilder(); + output.AppendLine($">> [{e.LogMessage.Timestamp:O}] [{e.LogMessage.ThreadId}] [{e.LogMessage.Source}] [{e.LogMessage.Level}]: {e.LogMessage.Message}"); + if (e.LogMessage.Exception != null) { - lock (_lock) - { - var backupColor = Console.ForegroundColor; - Console.ForegroundColor = color; - Console.Write(message); - Console.ForegroundColor = backupColor; - } + output.AppendLine(e.LogMessage.Exception.ToString()); } - static void PrintToConsole(object sender, MqttNetLogMessagePublishedEventArgs e) + var color = ConsoleColor.Red; + switch (e.LogMessage.Level) { - var output = new StringBuilder(); - output.AppendLine($">> [{e.LogMessage.Timestamp:O}] [{e.LogMessage.ThreadId}] [{e.LogMessage.Source}] [{e.LogMessage.Level}]: {e.LogMessage.Message}"); - if (e.LogMessage.Exception != null) - { - output.AppendLine(e.LogMessage.Exception.ToString()); - } - - var color = ConsoleColor.Red; - switch (e.LogMessage.Level) - { - case MqttNetLogLevel.Error: - color = ConsoleColor.Red; - break; - case MqttNetLogLevel.Warning: - color = ConsoleColor.Yellow; - break; - case MqttNetLogLevel.Info: - color = ConsoleColor.Green; - break; - case MqttNetLogLevel.Verbose: - color = ConsoleColor.Gray; - break; - } - - PrintToConsole(output.ToString(), color); + case MqttNetLogLevel.Error: + color = ConsoleColor.Red; + break; + case MqttNetLogLevel.Warning: + color = ConsoleColor.Yellow; + break; + case MqttNetLogLevel.Info: + color = ConsoleColor.Green; + break; + case MqttNetLogLevel.Verbose: + color = ConsoleColor.Gray; + break; } + + PrintToConsole(output.ToString(), color); } } \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/PerformanceTest.cs b/Source/MQTTnet.TestApp/PerformanceTest.cs index 644803243..472d2c403 100644 --- a/Source/MQTTnet.TestApp/PerformanceTest.cs +++ b/Source/MQTTnet.TestApp/PerformanceTest.cs @@ -12,355 +12,354 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class PerformanceTest { - public static class PerformanceTest + public static void RunClientOnly() { - public static void RunClientOnly() + try { - try + var options = new MqttClientOptions { - var options = new MqttClientOptions + ChannelOptions = new MqttClientTcpOptions { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) - }, - CleanSession = true - }; + RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) + }, + CleanSession = true + }; - var client = new MqttClientFactory().CreateMqttClient(); - client.ConnectAsync(options).GetAwaiter().GetResult(); + var client = new MqttClientFactory().CreateMqttClient(); + client.ConnectAsync(options).GetAwaiter().GetResult(); - var message = CreateMessage(); - var stopwatch = new Stopwatch(); + var message = CreateMessage(); + var stopwatch = new Stopwatch(); - for (var i = 0; i < 10; i++) - { - var sentMessagesCount = 0; + for (var i = 0; i < 10; i++) + { + var sentMessagesCount = 0; - stopwatch.Restart(); - while (stopwatch.ElapsedMilliseconds < 1000) - { - client.PublishAsync(message).GetAwaiter().GetResult(); - sentMessagesCount++; - } + stopwatch.Restart(); + while (stopwatch.ElapsedMilliseconds < 1000) + { + client.PublishAsync(message).GetAwaiter().GetResult(); + sentMessagesCount++; + } - Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1)); + Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1)); - GC.Collect(); - } - } - catch (Exception exception) - { - Console.WriteLine(exception); + GC.Collect(); } } + catch (Exception exception) + { + Console.WriteLine(exception); + } + } - public static async Task RunClientAndServer() + public static async Task RunClientAndServer() + { + try { - try - { - var mqttClientFactory = new MqttClientFactory(); - var mqttServerFactory = new MqttServerFactory(); - var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); - await mqttServer.StartAsync().ConfigureAwait(false); + var mqttClientFactory = new MqttClientFactory(); + var mqttServerFactory = new MqttServerFactory(); + var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); + var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions); + await mqttServer.StartAsync().ConfigureAwait(false); - var options = new MqttClientOptions + var options = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) - } - }; - - var client = mqttClientFactory.CreateMqttClient(); - await client.ConnectAsync(options).ConfigureAwait(false); + RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) + } + }; - var message = new MqttApplicationMessageBuilder().WithTopic("t") - .Build(); + var client = mqttClientFactory.CreateMqttClient(); + await client.ConnectAsync(options).ConfigureAwait(false); - var stopwatch = new Stopwatch(); + var message = new MqttApplicationMessageBuilder().WithTopic("t") + .Build(); - for (var i = 0; i < 10; i++) - { - stopwatch.Restart(); + var stopwatch = new Stopwatch(); - var sentMessagesCount = 0; - while (stopwatch.ElapsedMilliseconds < 1000) - { - await client.PublishAsync(message, CancellationToken.None).ConfigureAwait(false); - sentMessagesCount++; - } + for (var i = 0; i < 10; i++) + { + stopwatch.Restart(); - Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1)); + var sentMessagesCount = 0; + while (stopwatch.ElapsedMilliseconds < 1000) + { + await client.PublishAsync(message, CancellationToken.None).ConfigureAwait(false); + sentMessagesCount++; } - } - catch (Exception exception) - { - Console.WriteLine(exception); + + Console.WriteLine($"Sending {sentMessagesCount} messages per second. #" + (i + 1)); } } - - static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, bool concurrent) + catch (Exception exception) { - return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval, concurrent)))); + Console.WriteLine(exception); } + } + + static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, bool concurrent) + { + return Task.WhenAll(Enumerable.Range(0, 3).Select(_ => Task.Run(() => RunClientAsync(msgChunkSize, interval, concurrent)))); + } - static async Task RunClientAsync(int msgChunkSize, TimeSpan interval, bool concurrent) + static async Task RunClientAsync(int msgChunkSize, TimeSpan interval, bool concurrent) + { + try { - try + var options = new MqttClientOptions { - var options = new MqttClientOptions + ChannelOptions = new MqttClientTcpOptions { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("localhost", 0) - }, - ClientId = "Client1", - CleanSession = true - }; + RemoteEndpoint = new DnsEndPoint("localhost", 0) + }, + ClientId = "Client1", + CleanSession = true + }; - var client = new MqttClientFactory().CreateMqttClient(); + var client = new MqttClientFactory().CreateMqttClient(); - try - { - await client.ConnectAsync(options).ConfigureAwait(false); - } - catch (Exception exception) - { - Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); - } + try + { + await client.ConnectAsync(options).ConfigureAwait(false); + } + catch (Exception exception) + { + Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); + } - var message = CreateMessage(); - var stopwatch = Stopwatch.StartNew(); + var message = CreateMessage(); + var stopwatch = Stopwatch.StartNew(); - var testMessageCount = 10000; - for (var i = 0; i < testMessageCount; i++) - { - await client.PublishAsync(message); - } + var testMessageCount = 10000; + for (var i = 0; i < testMessageCount; i++) + { + await client.PublishAsync(message); + } - stopwatch.Stop(); - Console.WriteLine($"Sent 10.000 messages within {stopwatch.ElapsedMilliseconds} ms ({stopwatch.ElapsedMilliseconds / (float)testMessageCount} ms / message)."); + stopwatch.Stop(); + Console.WriteLine($"Sent 10.000 messages within {stopwatch.ElapsedMilliseconds} ms ({stopwatch.ElapsedMilliseconds / (float)testMessageCount} ms / message)."); - var last = DateTime.Now; - var msgCount = 0; + var last = DateTime.Now; + var msgCount = 0; - while (true) + while (true) + { + var msgs = Enumerable.Range(0, msgChunkSize) + .Select(_ => CreateMessage()) + .ToList(); + + if (concurrent) { - var msgs = Enumerable.Range(0, msgChunkSize) - .Select(i => CreateMessage()) + //send concurrent (test for raceconditions) + var sendTasks = msgs + .Select(msg => PublishSingleMessage(client, msg, ref msgCount)) .ToList(); - if (concurrent) - { - //send concurrent (test for raceconditions) - var sendTasks = msgs - .Select(msg => PublishSingleMessage(client, msg, ref msgCount)) - .ToList(); - - await Task.WhenAll(sendTasks); - } - else - { - foreach (var msg in msgs) - { - await client.PublishAsync(msg); - msgCount += msgs.Count; - //send multiple - } - } - - var now = DateTime.Now; - if (last < now - TimeSpan.FromSeconds(1)) + await Task.WhenAll(sendTasks); + } + else + { + foreach (var msg in msgs) { - Console.WriteLine($"sending {msgCount} intended {msgChunkSize / interval.TotalSeconds}"); - msgCount = 0; - last = now; + await client.PublishAsync(msg); + msgCount += msgs.Count; + //send multiple } + } - await Task.Delay(interval).ConfigureAwait(false); + var now = DateTime.Now; + if (last < now - TimeSpan.FromSeconds(1)) + { + Console.WriteLine($"sending {msgCount} intended {msgChunkSize / interval.TotalSeconds}"); + msgCount = 0; + last = now; } - } - catch (Exception exception) - { - Console.WriteLine(exception); + + await Task.Delay(interval).ConfigureAwait(false); } } - - static MqttApplicationMessage CreateMessage() + catch (Exception exception) { - //const string Payload = "###############################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################" - //const string Payload = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - const string Payload = "Hello World"; - - return new MqttApplicationMessage - { - Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(Payload)), - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce - }; + Console.WriteLine(exception); } + } - static Task PublishSingleMessage(IMqttClient client, MqttApplicationMessage applicationMessage, ref int count) - { - Interlocked.Increment(ref count); - return Task.Run(() => client.PublishAsync(applicationMessage)); - } + static MqttApplicationMessage CreateMessage() + { + //const string Payload = "###############################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################" + //const string Payload = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const string payload = "Hello World"; - public static async Task RunQoS2Test() + return new MqttApplicationMessage { - try - { - var mqttServer = new MqttServerFactory().CreateMqttServer(new MqttServerOptions()); - await mqttServer.StartAsync(); + Topic = "A/B/C", + PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(payload)), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce + }; + } - var options = new MqttClientOptions - { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) - }, - CleanSession = true - }; + static Task PublishSingleMessage(IMqttClient client, MqttApplicationMessage applicationMessage, ref int count) + { + Interlocked.Increment(ref count); + return Task.Run(() => client.PublishAsync(applicationMessage)); + } - var client = new MqttClientFactory().CreateMqttClient(); - await client.ConnectAsync(options); + public static async Task RunQoS2Test() + { + try + { + var mqttServer = new MqttServerFactory().CreateMqttServer(new MqttServerOptions()); + await mqttServer.StartAsync(); - var message = new MqttApplicationMessage + var options = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions { - Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce - }; + RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) + }, + CleanSession = true + }; - var stopwatch = new Stopwatch(); + var client = new MqttClientFactory().CreateMqttClient(); + await client.ConnectAsync(options); - var iteration = 1; - while (true) - { - var sentMessagesCount = 0; + var message = new MqttApplicationMessage + { + Topic = "A/B/C", + PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), + QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce + }; - stopwatch.Restart(); - while (stopwatch.ElapsedMilliseconds < 1000) - { - await client.PublishAsync(message).ConfigureAwait(false); - sentMessagesCount++; - } + var stopwatch = new Stopwatch(); - Console.WriteLine($"Sent {sentMessagesCount} messages in iteration #" + iteration); + var iteration = 1; + while (true) + { + var sentMessagesCount = 0; - iteration++; + stopwatch.Restart(); + while (stopwatch.ElapsedMilliseconds < 1000) + { + await client.PublishAsync(message).ConfigureAwait(false); + sentMessagesCount++; } - } - catch (Exception exception) - { - Console.WriteLine(exception); + + Console.WriteLine($"Sent {sentMessagesCount} messages in iteration #" + iteration); + + iteration++; } } + catch (Exception exception) + { + Console.WriteLine(exception); + } + } - public static async Task RunQoS1Test() + public static async Task RunQoS1Test() + { + try { - try - { - var mqttServer = new MqttServerFactory().CreateMqttServer(new MqttServerOptions()); - await mqttServer.StartAsync(); + var mqttServer = new MqttServerFactory().CreateMqttServer(new MqttServerOptions()); + await mqttServer.StartAsync(); - var options = new MqttClientOptions + var options = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) - }, - CleanSession = true - }; + RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) + }, + CleanSession = true + }; - var client = new MqttClientFactory().CreateMqttClient(); - await client.ConnectAsync(options); + var client = new MqttClientFactory().CreateMqttClient(); + await client.ConnectAsync(options); - var message = new MqttApplicationMessage - { - Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce - }; + var message = new MqttApplicationMessage + { + Topic = "A/B/C", + PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce + }; - var stopwatch = new Stopwatch(); + var stopwatch = new Stopwatch(); - var iteration = 1; - while (true) - { - var sentMessagesCount = 0; + var iteration = 1; + while (true) + { + var sentMessagesCount = 0; - stopwatch.Restart(); - while (stopwatch.ElapsedMilliseconds < 1000) - { - await client.PublishAsync(message).ConfigureAwait(false); - sentMessagesCount++; - } + stopwatch.Restart(); + while (stopwatch.ElapsedMilliseconds < 1000) + { + await client.PublishAsync(message).ConfigureAwait(false); + sentMessagesCount++; + } - Console.WriteLine($"Sent {sentMessagesCount} messages in iteration #" + iteration); + Console.WriteLine($"Sent {sentMessagesCount} messages in iteration #" + iteration); - iteration++; - } - } - catch (Exception exception) - { - Console.WriteLine(exception); + iteration++; } } + catch (Exception exception) + { + Console.WriteLine(exception); + } + } - public static async Task RunQoS0Test() + public static async Task RunQoS0Test() + { + try { - try - { - //var mqttServer = new MqttFactory().CreateMqttServer(); - //await mqttServer.StartAsync(new MqttServerOptions()); + //var mqttServer = new MqttFactory().CreateMqttServer(); + //await mqttServer.StartAsync(new MqttServerOptions()); - var options = new MqttClientOptions + var options = new MqttClientOptions + { + ChannelOptions = new MqttClientTcpOptions { - ChannelOptions = new MqttClientTcpOptions - { - RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) - }, - CleanSession = true - }; + RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0) + }, + CleanSession = true + }; - var client = new MqttClientFactory().CreateMqttClient(); - await client.ConnectAsync(options); + var client = new MqttClientFactory().CreateMqttClient(); + await client.ConnectAsync(options); - var message = new MqttApplicationMessage - { - Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce - }; + var message = new MqttApplicationMessage + { + Topic = "A/B/C", + PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce + }; - var stopwatch = new Stopwatch(); + var stopwatch = new Stopwatch(); - var iteration = 1; - while (true) - { - var sentMessagesCount = 0; + var iteration = 1; + while (true) + { + var sentMessagesCount = 0; - stopwatch.Restart(); - while (stopwatch.ElapsedMilliseconds < 1000) - { - await client.PublishAsync(message).ConfigureAwait(false); - sentMessagesCount++; - } + stopwatch.Restart(); + while (stopwatch.ElapsedMilliseconds < 1000) + { + await client.PublishAsync(message).ConfigureAwait(false); + sentMessagesCount++; + } - Console.WriteLine($"Sent {sentMessagesCount} messages in iteration #" + iteration); + Console.WriteLine($"Sent {sentMessagesCount} messages in iteration #" + iteration); - iteration++; - } - } - catch (Exception exception) - { - Console.WriteLine(exception); + iteration++; } } + catch (Exception exception) + { + Console.WriteLine(exception); + } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/PublicBrokerTest.cs b/Source/MQTTnet.TestApp/PublicBrokerTest.cs index 9e2d8bae6..21c55fdc5 100644 --- a/Source/MQTTnet.TestApp/PublicBrokerTest.cs +++ b/Source/MQTTnet.TestApp/PublicBrokerTest.cs @@ -10,173 +10,172 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class PublicBrokerTest { - public static class PublicBrokerTest + public static async Task RunAsync() { - public static async Task RunAsync() + // TLS13 is only available in Net5.0 + var unsafeTls13 = new MqttClientTlsOptions { - // TLS13 is only available in Net5.0 - var unsafeTls13 = new MqttClientTlsOptions - { - UseTls = true, - SslProtocol = SslProtocols.Tls13, - // Don't use this in production code. This handler simply allows any invalid certificate to work. - AllowUntrustedCertificates = true, - IgnoreCertificateChainErrors = true, - CertificateValidationHandler = _ => true - }; - - // Also defining TLS12 for servers that don't seem no to support TLS13. - var unsafeTls12 = new MqttClientTlsOptions - { - UseTls = true, - SslProtocol = SslProtocols.Tls12, - // Don't use this in production code. This handler simply allows any invalid certificate to work. - AllowUntrustedCertificates = true, - IgnoreCertificateChainErrors = true, - CertificateValidationHandler = _ => true - }; - - // mqtt.eclipseprojects.io - await ExecuteTestsAsync( - "mqtt.eclipseprojects.io TCP", - new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "mqtt.eclipseprojects.io WS", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync("mqtt.eclipseprojects.io WS TLS13", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt")) - .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); - - // test.mosquitto.org - await ExecuteTestsAsync( - "test.mosquitto.org TCP", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "test.mosquitto.org TCP - Authenticated", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "test.mosquitto.org TCP TLS12", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); - - await ExecuteTestsAsync("test.mosquitto.org TCP TLS13", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) - .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); - - await ExecuteTestsAsync( - "test.mosquitto.org TCP TLS12 - Authenticated", - new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885) - .WithCredentials("rw", "readwrite") - .WithProtocolVersion(MqttProtocolVersion.V311) - .WithTlsOptions(unsafeTls12) - .Build()); - - await ExecuteTestsAsync( - "test.mosquitto.org WS", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "test.mosquitto.org WS TLS12", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); - - // broker.emqx.io - await ExecuteTestsAsync( - "broker.emqx.io TCP", - new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "broker.emqx.io TCP TLS12", - new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); - - await ExecuteTestsAsync("broker.emqx.io TCP TLS13", - new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883) - .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); - - await ExecuteTestsAsync( - "broker.emqx.io WS", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "broker.emqx.io WS TLS12", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); - - // broker.hivemq.com - await ExecuteTestsAsync( - "broker.hivemq.com TCP", - new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - await ExecuteTestsAsync( - "broker.hivemq.com WS", - new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); - - // mqtt.swifitch.cz: Does not seem to operate any more - // cloudmqtt.com: Cannot test because it does not offer a free plan any more. - - Write("Finished.", ConsoleColor.White); - Console.ReadLine(); - } - - static async Task ExecuteTestsAsync(string name, MqttClientOptions options) + UseTls = true, + SslProtocol = SslProtocols.Tls13, + // Don't use this in production code. This handler simply allows any invalid certificate to work. + AllowUntrustedCertificates = true, + IgnoreCertificateChainErrors = true, + CertificateValidationHandler = _ => true + }; + + // Also defining TLS12 for servers that don't seem no to support TLS13. + var unsafeTls12 = new MqttClientTlsOptions { - options.ProtocolVersion = MqttProtocolVersion.V311; - await ExecuteTestAsync(name + " V3.1.1", options); + UseTls = true, + SslProtocol = SslProtocols.Tls12, + // Don't use this in production code. This handler simply allows any invalid certificate to work. + AllowUntrustedCertificates = true, + IgnoreCertificateChainErrors = true, + CertificateValidationHandler = _ => true + }; + + // mqtt.eclipseprojects.io + await ExecuteTestsAsync( + "mqtt.eclipseprojects.io TCP", + new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "mqtt.eclipseprojects.io WS", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync("mqtt.eclipseprojects.io WS TLS13", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt")) + .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); + + // test.mosquitto.org + await ExecuteTestsAsync( + "test.mosquitto.org TCP", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "test.mosquitto.org TCP - Authenticated", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "test.mosquitto.org TCP TLS12", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); + + await ExecuteTestsAsync("test.mosquitto.org TCP TLS13", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) + .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); + + await ExecuteTestsAsync( + "test.mosquitto.org TCP TLS12 - Authenticated", + new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885) + .WithCredentials("rw", "readwrite") + .WithProtocolVersion(MqttProtocolVersion.V311) + .WithTlsOptions(unsafeTls12) + .Build()); + + await ExecuteTestsAsync( + "test.mosquitto.org WS", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "test.mosquitto.org WS TLS12", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); + + // broker.emqx.io + await ExecuteTestsAsync( + "broker.emqx.io TCP", + new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "broker.emqx.io TCP TLS12", + new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); + + await ExecuteTestsAsync("broker.emqx.io TCP TLS13", + new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883) + .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); + + await ExecuteTestsAsync( + "broker.emqx.io WS", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "broker.emqx.io WS TLS12", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); + + // broker.hivemq.com + await ExecuteTestsAsync( + "broker.hivemq.com TCP", + new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + await ExecuteTestsAsync( + "broker.hivemq.com WS", + new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); + + // mqtt.swifitch.cz: Does not seem to operate any more + // cloudmqtt.com: Cannot test because it does not offer a free plan any more. + + Write("Finished.", ConsoleColor.White); + Console.ReadLine(); + } - options.ProtocolVersion = MqttProtocolVersion.V500; - await ExecuteTestAsync(name + " V5.0.0", options); - } + static async Task ExecuteTestsAsync(string name, MqttClientOptions options) + { + options.ProtocolVersion = MqttProtocolVersion.V311; + await ExecuteTestAsync(name + " V3.1.1", options); - static async Task ExecuteTestAsync(string name, MqttClientOptions options) + options.ProtocolVersion = MqttProtocolVersion.V500; + await ExecuteTestAsync(name + " V5.0.0", options); + } + + static async Task ExecuteTestAsync(string name, MqttClientOptions options) + { + try { - try - { - Write("Testing '" + name + "'... ", ConsoleColor.Gray); + Write("Testing '" + name + "'... ", ConsoleColor.Gray); - var factory = new MqttClientFactory(); + var factory = new MqttClientFactory(); - using (var client = factory.CreateMqttClient()) + using (var client = factory.CreateMqttClient()) + { + MqttApplicationMessage receivedMessage = null; + client.ApplicationMessageReceivedAsync += e => { - MqttApplicationMessage receivedMessage = null; - client.ApplicationMessageReceivedAsync += e => - { - receivedMessage = e.ApplicationMessage; - return CompletedTask.Instance; - }; - - await client.ConnectAsync(options).ConfigureAwait(false); + receivedMessage = e.ApplicationMessage; + return CompletedTask.Instance; + }; - var topic = Guid.NewGuid().ToString(); - await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false); - await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false); + await client.ConnectAsync(options).ConfigureAwait(false); - SpinWait.SpinUntil(() => receivedMessage != null, 5000); + var topic = Guid.NewGuid().ToString(); + await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false); + await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false); - if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World") - { - throw new Exception("Message invalid."); - } + SpinWait.SpinUntil(() => receivedMessage != null, 5000); - await client.UnsubscribeAsync(topic).ConfigureAwait(false); - await client.DisconnectAsync().ConfigureAwait(false); + if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World") + { + throw new Exception("Message invalid."); } - Write("[OK]\n", ConsoleColor.Green); - } - catch (Exception exception) - { - Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red); - Write(exception + Environment.NewLine, ConsoleColor.Red); + await client.UnsubscribeAsync(topic).ConfigureAwait(false); + await client.DisconnectAsync().ConfigureAwait(false); } - } - static void Write(string message, ConsoleColor color) + Write("[OK]\n", ConsoleColor.Green); + } + catch (Exception exception) { - Console.ForegroundColor = color; - Console.Write(message); + Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red); + Write(exception + Environment.NewLine, ConsoleColor.Red); } } + + static void Write(string message, ConsoleColor color) + { + Console.ForegroundColor = color; + Console.Write(message); + } } \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/ServerAndClientTest.cs b/Source/MQTTnet.TestApp/ServerAndClientTest.cs index cd0b0be1f..d43c8f9ce 100644 --- a/Source/MQTTnet.TestApp/ServerAndClientTest.cs +++ b/Source/MQTTnet.TestApp/ServerAndClientTest.cs @@ -7,26 +7,25 @@ using MQTTnet.Diagnostics.Logger; using MQTTnet.Server; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class ServerAndClientTest { - public static class ServerAndClientTest + public static async Task RunAsync() { - public static async Task RunAsync() - { - var logger = new MqttNetEventLogger(); - MqttNetConsoleLogger.ForwardToConsole(logger); + var logger = new MqttNetEventLogger(); + MqttNetConsoleLogger.ForwardToConsole(logger); - var mqttServerFactory = new MqttServerFactory(); - var mqttClientFactory = new MqttClientFactory(logger); - var server = mqttServerFactory.CreateMqttServer( new MqttServerOptionsBuilder().Build()); - var client = mqttClientFactory.CreateMqttClient(); + var mqttServerFactory = new MqttServerFactory(); + var mqttClientFactory = new MqttClientFactory(logger); + var server = mqttServerFactory.CreateMqttServer( new MqttServerOptionsBuilder().Build()); + var client = mqttClientFactory.CreateMqttClient(); - await server.StartAsync(); + await server.StartAsync(); - var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); - await client.ConnectAsync(clientOptions); + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); + await client.ConnectAsync(clientOptions); - await Task.Delay(Timeout.Infinite); - } + await Task.Delay(Timeout.Infinite); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/ServerTest.cs b/Source/MQTTnet.TestApp/ServerTest.cs index 241abcca3..ae0e0975c 100644 --- a/Source/MQTTnet.TestApp/ServerTest.cs +++ b/Source/MQTTnet.TestApp/ServerTest.cs @@ -13,189 +13,188 @@ using MQTTnet.Server; using Newtonsoft.Json; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public static class ServerTest { - public static class ServerTest + public static void RunEmptyServer() { - public static void RunEmptyServer() - { - var mqttServer = new MqttServerFactory().CreateMqttServer(new MqttServerOptions()); - mqttServer.StartAsync().GetAwaiter().GetResult(); + var mqttServer = new MqttServerFactory().CreateMqttServer(new MqttServerOptions()); + mqttServer.StartAsync().GetAwaiter().GetResult(); - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - } + Console.WriteLine("Press any key to exit."); + Console.ReadLine(); + } - public static void RunEmptyServerWithLogging() - { - var logger = new MqttNetEventLogger(); - MqttNetConsoleLogger.ForwardToConsole(logger); + public static void RunEmptyServerWithLogging() + { + var logger = new MqttNetEventLogger(); + MqttNetConsoleLogger.ForwardToConsole(logger); - var mqttServerFactory = new MqttServerFactory(logger); - var mqttServer = mqttServerFactory.CreateMqttServer(new MqttServerOptions()); - mqttServer.StartAsync().GetAwaiter().GetResult(); + var mqttServerFactory = new MqttServerFactory(logger); + var mqttServer = mqttServerFactory.CreateMqttServer(new MqttServerOptions()); + mqttServer.StartAsync().GetAwaiter().GetResult(); - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - } + Console.WriteLine("Press any key to exit."); + Console.ReadLine(); + } - public static async Task RunAsync() + public static async Task RunAsync() + { + try { - try - { - var options = new MqttServerOptionsBuilder() - .WithDefaultEndpoint() - .Build(); + var options = new MqttServerOptionsBuilder() + .WithDefaultEndpoint() + .Build(); - // Extend the timestamp for all messages from clients. - // Protect several topics from being subscribed from every client. + // Extend the timestamp for all messages from clients. + // Protect several topics from being subscribed from every client. - //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); - //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); - //options.ConnectionBacklog = 5; - //options.DefaultEndpointOptions.IsEnabled = true; - //options.TlsEndpointOptions.IsEnabled = false; + //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); + //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); + //options.ConnectionBacklog = 5; + //options.DefaultEndpointOptions.IsEnabled = true; + //options.TlsEndpointOptions.IsEnabled = false; - var mqttServer = new MqttServerFactory().CreateMqttServer(options); + var mqttServer = new MqttServerFactory().CreateMqttServer(options); - const string Filename = "C:\\MQTT\\RetainedMessages.json"; + const string filename = @"C:\MQTT\RetainedMessages.json"; - mqttServer.RetainedMessageChangedAsync += e => + mqttServer.RetainedMessageChangedAsync += e => + { + var directory = Path.GetDirectoryName(filename); + if (!Directory.Exists(directory)) { - var directory = Path.GetDirectoryName(Filename); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } + Directory.CreateDirectory(directory!); + } - File.WriteAllText(Filename, JsonConvert.SerializeObject(e.StoredRetainedMessages)); - return CompletedTask.Instance; - }; + File.WriteAllText(filename, JsonConvert.SerializeObject(e.StoredRetainedMessages)); + return CompletedTask.Instance; + }; - mqttServer.RetainedMessagesClearedAsync += e => - { - File.Delete(Filename); - return CompletedTask.Instance; - }; + mqttServer.RetainedMessagesClearedAsync += _ => + { + File.Delete(filename); + return CompletedTask.Instance; + }; - mqttServer.LoadingRetainedMessageAsync += e => + mqttServer.LoadingRetainedMessageAsync += e => + { + List retainedMessages; + if (File.Exists(filename)) { - List retainedMessages; - if (File.Exists(Filename)) - { - var json = File.ReadAllText(Filename); - retainedMessages = JsonConvert.DeserializeObject>(json); - } - else - { - retainedMessages = new List(); - } + var json = File.ReadAllText(filename); + retainedMessages = JsonConvert.DeserializeObject>(json); + } + else + { + retainedMessages = new List(); + } - e.LoadedRetainedMessages = retainedMessages; + e.LoadedRetainedMessages = retainedMessages; - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - mqttServer.InterceptingPublishAsync += e => + mqttServer.InterceptingPublishAsync += e => + { + if (MqttTopicFilterComparer.Compare(e.ApplicationMessage.Topic, "/myTopic/WithTimestamp/#") == MqttTopicFilterCompareResult.IsMatch) { - if (MqttTopicFilterComparer.Compare(e.ApplicationMessage.Topic, "/myTopic/WithTimestamp/#") == MqttTopicFilterCompareResult.IsMatch) - { - // Replace the payload with the timestamp. But also extending a JSON - // based payload with the timestamp is a suitable use case. - e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); - } - - if (e.ApplicationMessage.Topic == "not_allowed_topic") - { - e.ProcessPublish = false; - e.CloseConnection = true; - } - - return CompletedTask.Instance; - }; + // Replace the payload with the timestamp. But also extending a JSON + // based payload with the timestamp is a suitable use case. + e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); + } - mqttServer.ValidatingConnectionAsync += e => + if (e.ApplicationMessage.Topic == "not_allowed_topic") { - if (e.ClientId == "SpecialClient") - { - if (e.UserName != "USER" || e.Password != "PASS") - { - e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; - } - } + e.ProcessPublish = false; + e.CloseConnection = true; + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - mqttServer.InterceptingSubscriptionAsync += e => + mqttServer.ValidatingConnectionAsync += e => + { + if (e.ClientId == "SpecialClient") { - if (e.TopicFilter.Topic.StartsWith("admin/foo/bar") && e.ClientId != "theAdmin") - { - e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError; - } - - if (e.TopicFilter.Topic.StartsWith("the/secret/stuff") && e.ClientId != "Imperator") + if (e.UserName != "USER" || e.Password != "PASS") { - e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError; - e.CloseConnection = true; + e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; } + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - mqttServer.InterceptingPublishAsync += e => + mqttServer.InterceptingSubscriptionAsync += e => + { + if (e.TopicFilter.Topic.StartsWith("admin/foo/bar") && e.ClientId != "theAdmin") { - var payloadText = string.Empty; - if (e.ApplicationMessage.Payload.Length > 0) - { - payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); - } + e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError; + } - MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta); - return CompletedTask.Instance; - }; - - //options.ApplicationMessageInterceptor = c => - //{ - // if (c.ApplicationMessage.Payload == null || c.ApplicationMessage.Payload.Length == 0) - // { - // return; - // } - - // try - // { - // var content = JObject.Parse(Encoding.UTF8.GetString(c.ApplicationMessage.Payload)); - // var timestampProperty = content.Property("timestamp"); - // if (timestampProperty != null && timestampProperty.Value.Type == JTokenType.Null) - // { - // timestampProperty.Value = DateTime.Now.ToString("O"); - // c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); - // } - // } - // catch (Exception) - // { - // } - //}; - - mqttServer.ClientConnectedAsync += e => + if (e.TopicFilter.Topic.StartsWith("the/secret/stuff") && e.ClientId != "Imperator") { - Console.Write("Client disconnected event fired."); - return CompletedTask.Instance; - }; + e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError; + e.CloseConnection = true; + } - await mqttServer.StartAsync(); + return CompletedTask.Instance; + }; - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - - await mqttServer.StopAsync(); - } - catch (Exception e) + mqttServer.InterceptingPublishAsync += e => + { + var payloadText = string.Empty; + if (e.ApplicationMessage.Payload.Length > 0) + { + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); + } + + MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta); + return CompletedTask.Instance; + }; + + //options.ApplicationMessageInterceptor = c => + //{ + // if (c.ApplicationMessage.Payload == null || c.ApplicationMessage.Payload.Length == 0) + // { + // return; + // } + + // try + // { + // var content = JObject.Parse(Encoding.UTF8.GetString(c.ApplicationMessage.Payload)); + // var timestampProperty = content.Property("timestamp"); + // if (timestampProperty != null && timestampProperty.Value.Type == JTokenType.Null) + // { + // timestampProperty.Value = DateTime.Now.ToString("O"); + // c.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(content.ToString()); + // } + // } + // catch (Exception) + // { + // } + //}; + + mqttServer.ClientConnectedAsync += _ => { - Console.WriteLine(e); - } + Console.Write("Client disconnected event fired."); + return CompletedTask.Instance; + }; + await mqttServer.StartAsync(); + + Console.WriteLine("Press any key to exit."); Console.ReadLine(); + + await mqttServer.StopAsync(); } + catch (Exception e) + { + Console.WriteLine(e); + } + + Console.ReadLine(); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/TopicGenerator.cs b/Source/MQTTnet.TestApp/TopicGenerator.cs index ad7014a08..76ba1a619 100644 --- a/Source/MQTTnet.TestApp/TopicGenerator.cs +++ b/Source/MQTTnet.TestApp/TopicGenerator.cs @@ -2,97 +2,96 @@ using System.Collections.Generic; using MQTTnet.Extensions.TopicTemplate; -namespace MQTTnet.TestApp +namespace MQTTnet.TestApp; + +public class TopicGenerator { - public class TopicGenerator + public void Generate( + int numPublishers, int numTopicsPerPublisher, + out Dictionary> topicsByPublisher, + out Dictionary> singleWildcardTopicsByPublisher, + out Dictionary> multiWildcardTopicsByPublisher + ) { - public void Generate( - int numPublishers, int numTopicsPerPublisher, - out Dictionary> topicsByPublisher, - out Dictionary> singleWildcardTopicsByPublisher, - out Dictionary> multiWildcardTopicsByPublisher - ) - { - topicsByPublisher = new Dictionary>(); - singleWildcardTopicsByPublisher = new Dictionary>(); - multiWildcardTopicsByPublisher = new Dictionary>(); + topicsByPublisher = new Dictionary>(); + singleWildcardTopicsByPublisher = new Dictionary>(); + multiWildcardTopicsByPublisher = new Dictionary>(); - MqttTopicTemplate baseTemplate = new MqttTopicTemplate("{publisher}/{building}/{level}/{sensor}"); + MqttTopicTemplate baseTemplate = new MqttTopicTemplate("{publisher}/{building}/{level}/{sensor}"); - // Find some reasonable distribution across three topic levels - var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0)); + // Find some reasonable distribution across three topic levels + var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0)); - int numLevel1Topics = topicsPerLevel; - if (numLevel1Topics <= 0) - { - numLevel1Topics = 1; - } - int numLevel2Topics = topicsPerLevel; - if (numLevel2Topics <= 0) - { - numLevel2Topics = 1; - } - var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); - if (maxNumLevel3Topics <= 0) - { - maxNumLevel3Topics = 1; - } - for (var p = 0; p < numPublishers; ++p) - { - int publisherTopicCount = 0; + int numLevel1Topics = topicsPerLevel; + if (numLevel1Topics <= 0) + { + numLevel1Topics = 1; + } + int numLevel2Topics = topicsPerLevel; + if (numLevel2Topics <= 0) + { + numLevel2Topics = 1; + } + var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); + if (maxNumLevel3Topics <= 0) + { + maxNumLevel3Topics = 1; + } + for (var p = 0; p < numPublishers; ++p) + { + int publisherTopicCount = 0; - var publisherName = "pub" + p; - var publisherTemplate = baseTemplate - .WithParameter("publisher", publisherName); - for (var l1 = 0; l1 < numLevel1Topics; ++l1) + var publisherName = "pub" + p; + var publisherTemplate = baseTemplate + .WithParameter("publisher", publisherName); + for (var l1 = 0; l1 < numLevel1Topics; ++l1) + { + var l1Template = publisherTemplate + .WithParameter("building", "building" + (l1 + 1)); + for (var l2 = 0; l2 < numLevel2Topics; ++l2) { - var l1Template = publisherTemplate - .WithParameter("building", "building" + (l1 + 1)); - for (var l2 = 0; l2 < numLevel2Topics; ++l2) + var l2Template = l1Template + .WithParameter("level", "level" + (l2 + 1)); + for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) { - var l2Template = l1Template - .WithParameter("level", "level" + (l2 + 1)); - for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) - { - if (publisherTopicCount >= numTopicsPerPublisher) - break; + if (publisherTopicCount >= numTopicsPerPublisher) + break; - var topic = l2Template + var topic = l2Template + .WithParameter("sensor", "sensor" + (l3 + 1)) + .TopicFilter; + AddPublisherTopic(publisherName, topic, topicsByPublisher); + + if (l2 == 0) + { + var singleWildcardTopic = l1Template .WithParameter("sensor", "sensor" + (l3 + 1)) .TopicFilter; - AddPublisherTopic(publisherName, topic, topicsByPublisher); - - if (l2 == 0) + AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); + if (l1 == 0) { - var singleWildcardTopic = l1Template - .WithParameter("sensor", "sensor" + (l3 + 1)) - .TopicFilter; - AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); - if (l1 == 0) - { - var multiWildcardTopic = publisherTemplate - .WithParameter("sensor", "sensor" + (l3 + 1)) - .TopicFilter; - AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); - } + var multiWildcardTopic = publisherTemplate + .WithParameter("sensor", "sensor" + (l3 + 1)) + .TopicFilter; + AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); } - - ++publisherTopicCount; } + + ++publisherTopicCount; } } } } + } - void AddPublisherTopic(string publisherName, string topic, Dictionary> topicsByPublisher) + void AddPublisherTopic(string publisherName, string topic, Dictionary> topicsByPublisher) + { + List topicList; + if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) { - List topicList; - if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) - { - topicList = new List(); - topicsByPublisher.Add(publisherName, topicList); - } - topicList.Add(topic); + topicList = new List(); + topicsByPublisher.Add(publisherName, topicList); } + topicList.Add(topic); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/ASP/Mockups/DuplexPipeMockup.cs b/Source/MQTTnet.Tests/ASP/Mockups/DuplexPipeMockup.cs index 773000df6..a6a9195fd 100644 --- a/Source/MQTTnet.Tests/ASP/Mockups/DuplexPipeMockup.cs +++ b/Source/MQTTnet.Tests/ASP/Mockups/DuplexPipeMockup.cs @@ -4,24 +4,23 @@ using System.IO.Pipelines; -namespace MQTTnet.Tests.ASP.Mockups +namespace MQTTnet.Tests.ASP.Mockups; + +public sealed class DuplexPipeMockup : IDuplexPipe { - public sealed class DuplexPipeMockup : IDuplexPipe + public DuplexPipeMockup() { - public DuplexPipeMockup() - { - var pool = new LimitedMemoryPool(); - var pipeOptions = new PipeOptions(pool); - Receive = new Pipe(pipeOptions); - Send = new Pipe(pipeOptions); - } + var pool = new LimitedMemoryPool(); + var pipeOptions = new PipeOptions(pool); + Receive = new Pipe(pipeOptions); + Send = new Pipe(pipeOptions); + } - PipeReader IDuplexPipe.Input => Receive.Reader; + PipeReader IDuplexPipe.Input => Receive.Reader; - PipeWriter IDuplexPipe.Output => Send.Writer; + PipeWriter IDuplexPipe.Output => Send.Writer; - public Pipe Receive { get; } + public Pipe Receive { get; } - public Pipe Send { get; } - } -} + public Pipe Send { get; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/ASP/Mockups/LimitedMemoryPool.cs b/Source/MQTTnet.Tests/ASP/Mockups/LimitedMemoryPool.cs index acb1fd193..b97559541 100644 --- a/Source/MQTTnet.Tests/ASP/Mockups/LimitedMemoryPool.cs +++ b/Source/MQTTnet.Tests/ASP/Mockups/LimitedMemoryPool.cs @@ -4,19 +4,18 @@ using System.Buffers; -namespace MQTTnet.Tests.ASP.Mockups +namespace MQTTnet.Tests.ASP.Mockups; + +public sealed class LimitedMemoryPool : MemoryPool { - public sealed class LimitedMemoryPool : MemoryPool + protected override void Dispose(bool disposing) { - protected override void Dispose(bool disposing) - { - } - - public override IMemoryOwner Rent(int minBufferSize = -1) - { - return new MemoryOwner(minBufferSize); - } + } - public override int MaxBufferSize { get; } + public override IMemoryOwner Rent(int minBufferSize = -1) + { + return new MemoryOwner(minBufferSize); } + + public override int MaxBufferSize { get; } = 1; } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/ASP/Mockups/MemoryOwner.cs b/Source/MQTTnet.Tests/ASP/Mockups/MemoryOwner.cs index 6fc8a83d8..c16073027 100644 --- a/Source/MQTTnet.Tests/ASP/Mockups/MemoryOwner.cs +++ b/Source/MQTTnet.Tests/ASP/Mockups/MemoryOwner.cs @@ -5,33 +5,32 @@ using System; using System.Buffers; -namespace MQTTnet.Tests.ASP.Mockups +namespace MQTTnet.Tests.ASP.Mockups; + +public sealed class MemoryOwner : IMemoryOwner { - public sealed class MemoryOwner : IMemoryOwner - { - readonly byte[] _raw; + readonly byte[] _raw; - public MemoryOwner(int size) + public MemoryOwner(int size) + { + if (size <= 0) { - if (size <= 0) - { - size = 1024; - } - - if (size > 4096) - { - size = 4096; - } - - _raw = ArrayPool.Shared.Rent(size); - Memory = _raw; + size = 1024; } - public Memory Memory { get; } - - public void Dispose() + if (size > 4096) { - ArrayPool.Shared.Return(_raw); + size = 4096; } + + _raw = ArrayPool.Shared.Rent(size); + Memory = _raw; + } + + public Memory Memory { get; } + + public void Dispose() + { + ArrayPool.Shared.Return(_raw); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs index bfd0f8431..2f33f965b 100644 --- a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs +++ b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -17,114 +16,113 @@ using MQTTnet.Tests.ASP.Mockups; using MQTTnet.Tests.Helpers; -namespace MQTTnet.Tests.ASP +namespace MQTTnet.Tests.ASP; + +[TestClass] +public class MqttConnectionContextTest { - [TestClass] - public class MqttConnectionContextTest + [TestMethod] + public async Task TestCorruptedConnectPacket() { - [TestMethod] - public async Task TestCorruptedConnectPacket() - { - var writer = new MqttBufferWriter(4096, 65535); - var serializer = new MqttPacketFormatterAdapter(writer); - var pipe = new DuplexPipeMockup(); - var connection = new DefaultConnectionContext(); - connection.Transport = pipe; - var ctx = new MqttConnectionContext(serializer, connection); + var writer = new MqttBufferWriter(4096, 65535); + var serializer = new MqttPacketFormatterAdapter(writer); + var pipe = new DuplexPipeMockup(); + var connection = new DefaultConnectionContext(); + connection.Transport = pipe; + var ctx = new MqttConnectionContext(serializer, connection); - await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty())); + await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, [])); - await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None)); + await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None)); - // the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly - await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None)); - } + // the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly + await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None)).ConfigureAwait(false); + } - // TODO: Fix test - // [TestMethod] - // public async Task TestEndpoint() - // { - // var mockup = new ConnectionHandlerMockup(); - // - // using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx)))) - // .UseStartup() - // .ConfigureServices( - // (hostContext, services) => - // { - // services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint()); - // services.AddSingleton(mockup); - // }) - // .Build()) - // - // using (var client = new MqttFactory().CreateMqttClient()) - // { - // host.Start(); - // await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None); - // - // var ctx = await mockup.Context.Task; - // - // var ep = IPEndPoint.Parse(ctx.Endpoint); - // Assert.IsNotNull(ep); - // - // Assert.IsNotNull(ctx); - // } - // } - - // COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES! - //[TestMethod] - //public async Task TestParallelWrites() - //{ - // var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); - // var pipe = new DuplexPipeMockup(); - // var connection = new DefaultConnectionContext(); - // connection.Transport = pipe; - // var ctx = new MqttConnectionContext(serializer, connection); - - // var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () => - // { - // for (int i = 0; i < 100; i++) - // { - // await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false); - // } - // })); - - // await Task.WhenAll(tasks).ConfigureAwait(false); - //} - - [TestMethod] - public async Task TestLargePacket() - { - var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var pipe = new DuplexPipeMockup(); - var connection = new DefaultConnectionContext(); - connection.Transport = pipe; - var ctx = new MqttConnectionContext(serializer, connection); + // TODO: Fix test + // [TestMethod] + // public async Task TestEndpoint() + // { + // var mockup = new ConnectionHandlerMockup(); + // + // using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx)))) + // .UseStartup() + // .ConfigureServices( + // (hostContext, services) => + // { + // services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint()); + // services.AddSingleton(mockup); + // }) + // .Build()) + // + // using (var client = new MqttFactory().CreateMqttClient()) + // { + // host.Start(); + // await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None); + // + // var ctx = await mockup.Context.Task; + // + // var ep = IPEndPoint.Parse(ctx.Endpoint); + // Assert.IsNotNull(ep); + // + // Assert.IsNotNull(ctx); + // } + // } + + // COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES! + //[TestMethod] + //public async Task TestParallelWrites() + //{ + // var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); + // var pipe = new DuplexPipeMockup(); + // var connection = new DefaultConnectionContext(); + // connection.Transport = pipe; + // var ctx = new MqttConnectionContext(serializer, connection); + + // var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () => + // { + // for (int i = 0; i < 100; i++) + // { + // await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false); + // } + // })); + + // await Task.WhenAll(tasks).ConfigureAwait(false); + //} + + [TestMethod] + public async Task TestLargePacket() + { + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); + var pipe = new DuplexPipeMockup(); + var connection = new DefaultConnectionContext(); + connection.Transport = pipe; + var ctx = new MqttConnectionContext(serializer, connection); - await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false); + await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false); - var readResult = await pipe.Send.Reader.ReadAsync(); - Assert.IsTrue(readResult.Buffer.Length > 20000); - } + var readResult = await pipe.Send.Reader.ReadAsync(); + Assert.IsTrue(readResult.Buffer.Length > 20000); + } - [TestMethod] - public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted() - { - var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var pipe = new DuplexPipeMockup(); - var connection = new DefaultConnectionContext(); - connection.Transport = pipe; - var ctx = new MqttConnectionContext(serializer, connection); + [TestMethod] + public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted() + { + var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); + var pipe = new DuplexPipeMockup(); + var connection = new DefaultConnectionContext(); + connection.Transport = pipe; + var ctx = new MqttConnectionContext(serializer, connection); - pipe.Receive.Writer.Complete(); + await pipe.Receive.Writer.CompleteAsync(); - await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None)); - } + await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None)).ConfigureAwait(false); + } - class Startup + class Startup + { + public void Configure(IApplicationBuilder app) { - public void Configure(IApplicationBuilder app) - { - } } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs index 6c9cac8f8..ce0a82d6c 100644 --- a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs +++ b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs @@ -22,25 +22,20 @@ public void TestTryDeserialize() var sequence = new ReadOnlySequence(buffer.Array, buffer.Offset, buffer.Count); - var part = sequence; - var consumed = part.Start; - var observed = part.Start; - var read = 0; - - part = sequence.Slice(sequence.Start, 0); // empty message should fail - var result = serializer.TryDecode(part, out _, out consumed, out observed, out read); + var part = sequence.Slice(sequence.Start, 0); // empty message should fail + var result = serializer.TryDecode(part, out _, out _, out _, out _); Assert.IsFalse(result); part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail - result = serializer.TryDecode(part, out _, out consumed, out observed, out read); + result = serializer.TryDecode(part, out _, out _, out _, out _); Assert.IsFalse(result); part = sequence.Slice(sequence.Start, 4); // partial body should fail - result = serializer.TryDecode(part, out _, out consumed, out observed, out read); + result = serializer.TryDecode(part, out _, out _, out _, out _); Assert.IsFalse(result); part = sequence; // complete msg should work - result = serializer.TryDecode(part, out _, out consumed, out observed, out read); + result = serializer.TryDecode(part, out _, out _, out _, out _); Assert.IsTrue(result); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/BaseTestClass.cs b/Source/MQTTnet.Tests/BaseTestClass.cs index 00291b024..aabef5ae6 100644 --- a/Source/MQTTnet.Tests/BaseTestClass.cs +++ b/Source/MQTTnet.Tests/BaseTestClass.cs @@ -8,21 +8,20 @@ using MQTTnet.Formatter; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +public abstract class BaseTestClass { - public abstract class BaseTestClass - { - public TestContext TestContext { get; set; } + public TestContext TestContext { get; set; } - protected TestEnvironment CreateTestEnvironment( - MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, bool trackUnobservedTaskException = true) - { - return new TestEnvironment(TestContext, protocolVersion, trackUnobservedTaskException); - } + protected TestEnvironment CreateTestEnvironment( + MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, bool trackUnobservedTaskException = true) + { + return new TestEnvironment(TestContext, protocolVersion, trackUnobservedTaskException); + } - protected Task LongTestDelay() - { - return Task.Delay(TimeSpan.FromSeconds(1)); - } + protected Task LongTestDelay() + { + return Task.Delay(TimeSpan.FromSeconds(1)); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs index 4c8a44ad8..2e308a5b1 100644 --- a/Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs @@ -14,207 +14,194 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Clients.LowLevelMqttClient +namespace MQTTnet.Tests.Clients.LowLevelMqttClient; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class LowLevelMqttClient_Tests : BaseTestClass { - [TestClass] - public sealed class LowLevelMqttClient_Tests : BaseTestClass + [TestMethod] + public async Task Authenticate() { - [TestMethod] - public async Task Authenticate() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var factory = new MqttClientFactory(); - var lowLevelClient = factory.CreateLowLevelMqttClient(); + var factory = new MqttClientFactory(); + var lowLevelClient = factory.CreateLowLevelMqttClient(); - await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None); + await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None); - var receivedPacket = await Authenticate(lowLevelClient).ConfigureAwait(false); + var receivedPacket = await Authenticate(lowLevelClient).ConfigureAwait(false); - await lowLevelClient.DisconnectAsync(CancellationToken.None).ConfigureAwait(false); - - Assert.IsNotNull(receivedPacket); - Assert.AreEqual(MqttConnectReturnCode.ConnectionAccepted, receivedPacket.ReturnCode); - } - } + await lowLevelClient.DisconnectAsync(CancellationToken.None).ConfigureAwait(false); - [TestMethod] - public async Task Connect_And_Disconnect() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + Assert.IsNotNull(receivedPacket); + Assert.AreEqual(MqttConnectReturnCode.ConnectionAccepted, receivedPacket.ReturnCode); + } - var lowLevelClient = testEnvironment.ClientFactory.CreateLowLevelMqttClient(); + [TestMethod] + public async Task Connect_And_Disconnect() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None); + var lowLevelClient = testEnvironment.ClientFactory.CreateLowLevelMqttClient(); - await lowLevelClient.DisconnectAsync(CancellationToken.None); - } - } + await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None); - [TestMethod] - [ExpectedException(typeof(MqttCommunicationException))] - public async Task Connect_To_Not_Existing_Broker() - { - var client = new MqttClientFactory().CreateLowLevelMqttClient(); - var options = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); + await lowLevelClient.DisconnectAsync(CancellationToken.None); + } - await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); - } + [TestMethod] + [ExpectedException(typeof(MqttCommunicationException))] + public async Task Connect_To_Not_Existing_Broker() + { + var client = new MqttClientFactory().CreateLowLevelMqttClient(); + var options = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); - [TestMethod] - [ExpectedException(typeof(MqttCommunicationException))] - public async Task Connect_To_Wrong_Host() - { - var client = new MqttClientFactory().CreateLowLevelMqttClient(); - var options = new MqttClientOptionsBuilder().WithTcpServer("123.456.789.10").Build(); + await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); + } - await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); - } + [TestMethod] + [ExpectedException(typeof(MqttCommunicationException))] + public async Task Connect_To_Wrong_Host() + { + var client = new MqttClientFactory().CreateLowLevelMqttClient(); + var options = new MqttClientOptionsBuilder().WithTcpServer("123.456.789.10").Build(); - [TestMethod] - public async Task Loose_Connection() - { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreServerLogErrors = true; + await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); + } - testEnvironment.ServerPort = 8364; - var server = await testEnvironment.StartServer(); + [TestMethod] + public async Task Loose_Connection() + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreServerLogErrors = true; - var client = await testEnvironment.ConnectLowLevelClient(o => o.WithTimeout(TimeSpan.Zero)); + testEnvironment.ServerPort = 8364; + var server = await testEnvironment.StartServer(); - await Authenticate(client).ConfigureAwait(false); + var client = await testEnvironment.ConnectLowLevelClient(o => o.WithTimeout(TimeSpan.Zero)); - await server.StopAsync(); + await Authenticate(client).ConfigureAwait(false); - await Task.Delay(2000); + await server.StopAsync(); - try - { - await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None).ConfigureAwait(false); - await Task.Delay(2000); - await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None).ConfigureAwait(false); - } - catch (MqttCommunicationException exception) - { - Assert.IsTrue(exception.InnerException is SocketException); - return; - } - catch - { - Assert.Fail("Wrong exception type thrown."); - } + await Task.Delay(2000); - Assert.Fail("This MUST fail"); - } + try + { + await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None).ConfigureAwait(false); + await Task.Delay(2000); + await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None).ConfigureAwait(false); } - - [TestMethod] - public async Task Maintain_IsConnected_Property() + catch (MqttCommunicationException exception) + { + Assert.IsTrue(exception.InnerException is SocketException); + return; + } + catch { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreServerLogErrors = true; + Assert.Fail("Wrong exception type thrown."); + } - var server = await testEnvironment.StartServer(); + Assert.Fail("This MUST fail"); + } - using (var lowLevelClient = testEnvironment.CreateLowLevelClient()) - { - Assert.IsFalse(lowLevelClient.IsConnected); + [TestMethod] + public async Task Maintain_IsConnected_Property() + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreServerLogErrors = true; - var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithTimeout(TimeSpan.FromSeconds(1)).Build(); + var server = await testEnvironment.StartServer(); - await lowLevelClient.ConnectAsync(clientOptions, CancellationToken.None); + using var lowLevelClient = testEnvironment.CreateLowLevelClient(); + Assert.IsFalse(lowLevelClient.IsConnected); - Assert.IsTrue(lowLevelClient.IsConnected); + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithTimeout(TimeSpan.FromSeconds(1)).Build(); - await server.StopAsync(); - server.Dispose(); + await lowLevelClient.ConnectAsync(clientOptions, CancellationToken.None); - await LongTestDelay(); + Assert.IsTrue(lowLevelClient.IsConnected); - Assert.IsTrue(lowLevelClient.IsConnected); + await server.StopAsync(); + server.Dispose(); - try - { - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) - { - await lowLevelClient.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None); - await LongTestDelay(); + await LongTestDelay(); - await lowLevelClient.ReceiveAsync(timeout.Token); - } - } - catch - { - } + Assert.IsTrue(lowLevelClient.IsConnected); - Assert.IsFalse(lowLevelClient.IsConnected); - } - } - } + try + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await lowLevelClient.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None); + await LongTestDelay(); - [TestMethod] - public async Task Subscribe() + await lowLevelClient.ReceiveAsync(timeout.Token); + } + catch { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + // The connection is now closed! + } - var factory = new MqttClientFactory(); - var lowLevelClient = factory.CreateLowLevelMqttClient(); + Assert.IsFalse(lowLevelClient.IsConnected); + } - await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None); + [TestMethod] + public async Task Subscribe() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - await Authenticate(lowLevelClient).ConfigureAwait(false); + var factory = new MqttClientFactory(); + var lowLevelClient = factory.CreateLowLevelMqttClient(); - var receivedPacket = await Subscribe(lowLevelClient, "a").ConfigureAwait(false); + await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None); - await lowLevelClient.DisconnectAsync(CancellationToken.None).ConfigureAwait(false); + await Authenticate(lowLevelClient).ConfigureAwait(false); - Assert.IsNotNull(receivedPacket); - Assert.AreEqual(MqttSubscribeReasonCode.GrantedQoS0, receivedPacket.ReasonCodes[0]); - } - } + var receivedPacket = await Subscribe(lowLevelClient, "a").ConfigureAwait(false); - async Task Authenticate(ILowLevelMqttClient client) - { - await client.SendAsync( - new MqttConnectPacket - { - CleanSession = true, - ClientId = TestContext.TestName, - Username = "user", - Password = Encoding.UTF8.GetBytes("pass") - }, - CancellationToken.None) - .ConfigureAwait(false); - - return await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false) as MqttConnAckPacket; - } + await lowLevelClient.DisconnectAsync(CancellationToken.None).ConfigureAwait(false); - async Task Subscribe(ILowLevelMqttClient client, string topic) - { - await client.SendAsync( - new MqttSubscribePacket + Assert.IsNotNull(receivedPacket); + Assert.AreEqual(MqttSubscribeReasonCode.GrantedQoS0, receivedPacket.ReasonCodes[0]); + } + + async Task Authenticate(ILowLevelMqttClient client) + { + await client.SendAsync( + new MqttConnectPacket + { + CleanSession = true, + ClientId = TestContext.TestName, + Username = "user", + Password = Encoding.UTF8.GetBytes("pass") + }, + CancellationToken.None) + .ConfigureAwait(false); + + return await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false) as MqttConnAckPacket; + } + + async Task Subscribe(ILowLevelMqttClient client, string topic) + { + await client.SendAsync( + new MqttSubscribePacket + { + PacketIdentifier = 1, + TopicFilters = { - PacketIdentifier = 1, - TopicFilters = + new MqttTopicFilter { - new MqttTopicFilter - { - Topic = topic - } + Topic = topic } - }, - CancellationToken.None) - .ConfigureAwait(false); + } + }, + CancellationToken.None) + .ConfigureAwait(false); - return await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false) as MqttSubAckPacket; - } + return await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false) as MqttSubAckPacket; } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs index ef8d75699..91c375521 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs @@ -14,284 +14,284 @@ using MQTTnet.Server; using MQTTnet.Server.EnhancedAuthentication; -namespace MQTTnet.Tests.Clients.MqttClient +namespace MQTTnet.Tests.Clients.MqttClient; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttClient_Connection_Tests : BaseTestClass { - [TestClass] - public sealed class MqttClient_Connection_Tests : BaseTestClass + [TestMethod] + [ExpectedException(typeof(MqttCommunicationException))] + public async Task Connect_To_Invalid_Server_Port_Not_Opened() { - [TestMethod] - [ExpectedException(typeof(MqttCommunicationException))] - public async Task Connect_To_Invalid_Server_Port_Not_Opened() - { - var client = new MqttClientFactory().CreateMqttClient(); - using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token); - } + var client = new MqttClientFactory().CreateMqttClient(); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token); + } - [TestMethod] - [ExpectedException(typeof(OperationCanceledException))] - public async Task Connect_To_Invalid_Server_Wrong_IP() - { - var client = new MqttClientFactory().CreateMqttClient(); - using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token); - } + [TestMethod] + [ExpectedException(typeof(OperationCanceledException))] + public async Task Connect_To_Invalid_Server_Wrong_IP() + { + var client = new MqttClientFactory().CreateMqttClient(); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token); + } - [TestMethod] - [ExpectedException(typeof(MqttCommunicationException))] - public async Task Connect_To_Invalid_Server_Wrong_Protocol() - { - var client = new MqttClientFactory().CreateMqttClient(); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build()); - } + [TestMethod] + [ExpectedException(typeof(MqttCommunicationException))] + public async Task Connect_To_Invalid_Server_Wrong_Protocol() + { + var client = new MqttClientFactory().CreateMqttClient(); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build()).ConfigureAwait(false); + } - [TestMethod] - public async Task ConnectTimeout_Throws_Exception() + [TestMethod] + public async Task ConnectTimeout_Throws_Exception() + { + var factory = new MqttClientFactory(); + using var client = factory.CreateMqttClient(); + var disconnectHandlerCalled = false; + try { - var factory = new MqttClientFactory(); - using var client = factory.CreateMqttClient(); - var disconnectHandlerCalled = false; - try + client.DisconnectedAsync += _ => { - client.DisconnectedAsync += _ => - { - disconnectHandlerCalled = true; - return CompletedTask.Instance; - }; + disconnectHandlerCalled = true; + return CompletedTask.Instance; + }; - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build()); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build()); - Assert.Fail("Must fail!"); - } - catch (Exception exception) - { - Assert.IsNotNull(exception); - Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException)); - } - - await LongTestDelay(); // disconnected handler is called async - Assert.IsTrue(disconnectHandlerCalled); + Assert.Fail("Must fail!"); + } + catch (Exception exception) + { + Assert.IsNotNull(exception); + Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException)); } - [TestMethod] - public async Task Disconnect_Clean() + await LongTestDelay(); // disconnected handler is called async + Assert.IsTrue(disconnectHandlerCalled); + } + + [TestMethod] + public async Task Disconnect_Clean() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); + + ClientDisconnectedEventArgs eventArgs = null; + server.ClientDisconnectedAsync += args => { - using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); - var server = await testEnvironment.StartServer(); + eventArgs = args; + return CompletedTask.Instance; + }; - ClientDisconnectedEventArgs eventArgs = null; - server.ClientDisconnectedAsync += args => - { - eventArgs = args; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); - var client = await testEnvironment.ConnectClient(); + var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build(); - var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build(); + // Perform a clean disconnect. + await client.DisconnectAsync(disconnectOptions); - // Perform a clean disconnect. - await client.DisconnectAsync(disconnectOptions); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); + Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType); + } - Assert.IsNotNull(eventArgs); - Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType); - } + [TestMethod] + public async Task Disconnect_Clean_With_Custom_Reason() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Disconnect_Clean_With_Custom_Reason() + ClientDisconnectedEventArgs eventArgs = null; + server.ClientDisconnectedAsync += args => { - using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); - var server = await testEnvironment.StartServer(); + eventArgs = args; + return CompletedTask.Instance; + }; - ClientDisconnectedEventArgs eventArgs = null; - server.ClientDisconnectedAsync += args => - { - eventArgs = args; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); - var client = await testEnvironment.ConnectClient(); + var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build(); - var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build(); + // Perform a clean disconnect. + await client.DisconnectAsync(disconnectOptions); - // Perform a clean disconnect. - await client.DisconnectAsync(disconnectOptions); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); + Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode); + } - Assert.IsNotNull(eventArgs); - Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode); - } + [TestMethod] + public async Task Disconnect_Clean_With_User_Properties() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Disconnect_Clean_With_User_Properties() + ClientDisconnectedEventArgs eventArgs = null; + server.ClientDisconnectedAsync += args => { - using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); - var server = await testEnvironment.StartServer(); - - ClientDisconnectedEventArgs eventArgs = null; - server.ClientDisconnectedAsync += args => - { - eventArgs = args; - return CompletedTask.Instance; - }; + eventArgs = args; + return CompletedTask.Instance; + }; - var client = await testEnvironment.ConnectClient(); + var client = await testEnvironment.ConnectClient(); - var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build(); + var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build(); - // Perform a clean disconnect. - await client.DisconnectAsync(disconnectOptions); + // Perform a clean disconnect. + await client.DisconnectAsync(disconnectOptions); - await LongTestDelay(); + await LongTestDelay(); - Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs.UserProperties); - Assert.AreEqual(1, eventArgs.UserProperties.Count); - Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name); - Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value); - } + Assert.IsNotNull(eventArgs); + Assert.IsNotNull(eventArgs.UserProperties); + Assert.AreEqual(1, eventArgs.UserProperties.Count); + Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name); + Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value); + } - class TestClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler + class TestClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler + { + public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs) { - public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs) + if (eventArgs.AuthenticationMethod != "GS2-KRB5") { - if (eventArgs.AuthenticationMethod != "GS2-KRB5") - { - throw new InvalidOperationException("Wrong authentication method"); - } + throw new InvalidOperationException("Wrong authentication method"); + } - var sendOptions = new SendMqttEnhancedAuthenticationDataOptions - { - Data = "initial context token"u8.ToArray() - }; + var sendOptions = new SendMqttEnhancedAuthenticationDataOptions + { + Data = "initial context token"u8.ToArray() + }; - await eventArgs.SendAsync(sendOptions, eventArgs.CancellationToken); + await eventArgs.SendAsync(sendOptions, eventArgs.CancellationToken); - var response = await eventArgs.ReceiveAsync(eventArgs.CancellationToken); + var response = await eventArgs.ReceiveAsync(eventArgs.CancellationToken); - Assert.AreEqual(Encoding.UTF8.GetString(response.AuthenticationData), "reply context token"); + Assert.AreEqual(Encoding.UTF8.GetString(response.AuthenticationData), "reply context token"); - // No further data is required, but we have to fulfil the exchange. - sendOptions = new SendMqttEnhancedAuthenticationDataOptions - { - Data = [] - }; + // No further data is required, but we have to fulfil the exchange. + sendOptions = new SendMqttEnhancedAuthenticationDataOptions + { + Data = [] + }; - await eventArgs.SendAsync(sendOptions, eventArgs.CancellationToken); - } + await eventArgs.SendAsync(sendOptions, eventArgs.CancellationToken).ConfigureAwait(false); } + } - [TestMethod] - public async Task Use_Enhanced_Authentication() - { - using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); - var server = await testEnvironment.StartServer(); + [TestMethod] + public async Task Use_Enhanced_Authentication() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); - server.ValidatingConnectionAsync += async args => + server.ValidatingConnectionAsync += async args => + { + if (args.AuthenticationMethod == "GS2-KRB5") { - if (args.AuthenticationMethod == "GS2-KRB5") - { - var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken); + var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken); - Assert.AreEqual(Encoding.UTF8.GetString(result.AuthenticationData), "initial context token"); + Assert.AreEqual(Encoding.UTF8.GetString(result.AuthenticationData), "initial context token"); - var authOptions = testEnvironment.ServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build(); + var authOptions = testEnvironment.ServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build(); - result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken); + result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken); - Assert.AreEqual(Encoding.UTF8.GetString(result.AuthenticationData), ""); + Assert.AreEqual(Encoding.UTF8.GetString(result.AuthenticationData), ""); - args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray(); - } - else - { - args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod; - } - }; + args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray(); + } + else + { + args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod; + } + }; - // Use Kerberos sample from the MQTT RFC. - var kerberosAuthenticationHandler = new TestClientKerberosAuthenticationHandler(); + // Use Kerberos sample from the MQTT RFC. + var kerberosAuthenticationHandler = new TestClientKerberosAuthenticationHandler(); - var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithEnhancedAuthentication("GS2-KRB5").WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler); - var client = await testEnvironment.ConnectClient(clientOptions); + var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithEnhancedAuthentication("GS2-KRB5").WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler); + var client = await testEnvironment.ConnectClient(clientOptions); - Assert.IsTrue(client.IsConnected); - } + Assert.IsTrue(client.IsConnected); + } - [TestMethod] - public async Task No_Unobserved_Exception() - { - using var testEnvironment = CreateTestEnvironment(); - testEnvironment.IgnoreClientLogErrors = true; + [TestMethod] + public async Task No_Unobserved_Exception() + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; - var client = testEnvironment.CreateClient(); - var options = new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").WithTimeout(TimeSpan.FromSeconds(2)).Build(); + var client = testEnvironment.CreateClient(); + var options = new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").WithTimeout(TimeSpan.FromSeconds(2)).Build(); - try - { - using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(0.5)); - await client.ConnectAsync(options, timeout.Token); - } - catch (OperationCanceledException) - { - } + try + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(0.5)); + await client.ConnectAsync(options, timeout.Token); + } + catch (OperationCanceledException) + { + } - client.Dispose(); + client.Dispose(); - // These delays and GC calls are required in order to make calling the finalizer reproducible. - GC.Collect(); - GC.WaitForPendingFinalizers(); - await LongTestDelay(); - await LongTestDelay(); - await LongTestDelay(); - } + // These delays and GC calls are required in order to make calling the finalizer reproducible. + GC.Collect(); + GC.WaitForPendingFinalizers(); + await LongTestDelay(); + await LongTestDelay(); + await LongTestDelay(); + } - [TestMethod] - public async Task Return_Non_Success() + [TestMethod] + public async Task Return_Non_Success() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); + + server.ValidatingConnectionAsync += args => { - using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); - var server = await testEnvironment.StartServer(); + args.ResponseUserProperties = [new("Property", "Value")]; - server.ValidatingConnectionAsync += args => - { - args.ResponseUserProperties = [new("Property", "Value")]; + args.ReasonCode = MqttConnectReasonCode.QuotaExceeded; - args.ReasonCode = MqttConnectReasonCode.QuotaExceeded; + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + var client = testEnvironment.CreateClient(); - var client = testEnvironment.CreateClient(); + var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().Build()); - var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().Build()); + Assert.IsNotNull(response); + Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode); + Assert.AreEqual(response.UserProperties[0].Name, "Property"); + Assert.AreEqual(response.UserProperties[0].Value, "Value"); + } - Assert.IsNotNull(response); - Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode); - Assert.AreEqual(response.UserProperties[0].Name, "Property"); - Assert.AreEqual(response.UserProperties[0].Value, "Value"); + [TestMethod] + public async Task Throw_Proper_Exception_When_Not_Connected() + { + try + { + var mqttFactory = new MqttClientFactory(); + using var mqttClient = mqttFactory.CreateMqttClient(); + await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce); } - - [TestMethod] - public async Task Throw_Proper_Exception_When_Not_Connected() + catch (MqttClientNotConnectedException exception) { - try - { - var mqttFactory = new MqttClientFactory(); - using var mqttClient = mqttFactory.CreateMqttClient(); - await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce); - } - catch (MqttClientNotConnectedException exception) + if (exception.Message == "The MQTT client is not connected.") { - if (exception.Message == "The MQTT client is not connected.") - { - return; - } + return; } - - Assert.Fail(); } + + Assert.Fail(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index b8b1ba9e2..e43d1f27e 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -20,910 +20,857 @@ using MQTTnet.Server; using MQTTnet.Tests.Mockups; -// ReSharper disable InconsistentNaming +namespace MQTTnet.Tests.Clients.MqttClient; -namespace MQTTnet.Tests.Clients.MqttClient +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttClient_Tests : BaseTestClass { - [TestClass] - public sealed class MqttClient_Tests : BaseTestClass + [TestMethod] + [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] + [DataRow(MqttQualityOfServiceLevel.AtMostOnce)] + [DataRow(MqttQualityOfServiceLevel.AtLeastOnce)] + public async Task Concurrent_Processing(MqttQualityOfServiceLevel qos) { - [TestMethod] - [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] - [DataRow(MqttQualityOfServiceLevel.AtMostOnce)] - [DataRow(MqttQualityOfServiceLevel.AtLeastOnce)] - public async Task Concurrent_Processing(MqttQualityOfServiceLevel qos) + long concurrency = 0; + var success = false; + + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var publisher = await testEnvironment.ConnectClient(); + var subscriber = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId(qos.ToString())); + await subscriber.SubscribeAsync("#", qos); + + subscriber.ApplicationMessageReceivedAsync += e => { - long concurrency = 0; - var success = false; + e.AutoAcknowledge = false; - using (var testEnvironment = new TestEnvironment(TestContext)) + async Task InvokeInternal() { - await testEnvironment.StartServer(); - var publisher = await testEnvironment.ConnectClient(); - var subscriber = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId(qos.ToString())); - await subscriber.SubscribeAsync("#", qos); - - subscriber.ApplicationMessageReceivedAsync += e => + if (Interlocked.Increment(ref concurrency) > 1) { - e.AutoAcknowledge = false; - - async Task InvokeInternal() - { - if (Interlocked.Increment(ref concurrency) > 1) - { - success = true; - } + success = true; + } - await Task.Delay(100); - Interlocked.Decrement(ref concurrency); - } + await Task.Delay(100); + Interlocked.Decrement(ref concurrency); + } - _ = InvokeInternal(); - return CompletedTask.Instance; - }; + _ = InvokeInternal(); + return CompletedTask.Instance; + }; - var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos)); + var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos)); - await Task.Delay(200); + await Task.Delay(200); - await publishes; - Assert.IsTrue(success); - } - } + await publishes; + Assert.IsTrue(success); + } - [TestMethod] - public async Task Connect_Disconnect_Connect() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Connect_Disconnect_Connect() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var clientOptions = testEnvironment.CreateDefaultClientOptions(); - var client = testEnvironment.CreateClient(); + var clientOptions = testEnvironment.CreateDefaultClientOptions(); + var client = testEnvironment.CreateClient(); - await client.ConnectAsync(clientOptions); - await client.DisconnectAsync(); - await client.ConnectAsync(clientOptions); - } - } + await client.ConnectAsync(clientOptions); + await client.DisconnectAsync(); + await client.ConnectAsync(clientOptions); + } - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public async Task Connect_Multiple_Times_Should_Fail() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task Connect_Multiple_Times_Should_Fail() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var clientOptions = testEnvironment.CreateDefaultClientOptions(); - var client = testEnvironment.CreateClient(); + var clientOptions = testEnvironment.CreateDefaultClientOptions(); + var client = testEnvironment.CreateClient(); - await client.ConnectAsync(clientOptions); - await client.ConnectAsync(clientOptions); - } - } + await client.ConnectAsync(clientOptions); + await client.ConnectAsync(clientOptions); + } - [TestMethod] - public async Task Disconnect_Event_Contains_Exception() + [TestMethod] + public async Task Disconnect_Event_Contains_Exception() + { + var factory = new MqttClientFactory(); + using var client = factory.CreateMqttClient(); + Exception ex = null; + client.DisconnectedAsync += e => { - var factory = new MqttClientFactory(); - using (var client = factory.CreateMqttClient()) - { - Exception ex = null; - client.DisconnectedAsync += e => - { - ex = e.Exception; - return CompletedTask.Instance; - }; + ex = e.Exception; + return CompletedTask.Instance; + }; - try - { - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); - } - catch - { - // Ignore errors. - } - - await Task.Delay(500); - - Assert.IsNotNull(ex); - Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException)); - Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException)); - } + try + { + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); } - - [TestMethod] - public async Task Ensure_Queue_Drain() + catch { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectLowLevelClient(); + // Ignore errors. + } - var i = 0; - server.InterceptingPublishAsync += c => - { - i++; - return CompletedTask.Instance; - }; - - await client.SendAsync( - new MqttConnectPacket - { - ClientId = "Ensure_Queue_Drain_Test" - }, - CancellationToken.None); - - await client.SendAsync( - new MqttPublishPacket - { - Topic = "Test" - }, - CancellationToken.None); - - await Task.Delay(500); - - // This will simulate a device which closes the connection directly - // after sending the data so do delay is added between send and dispose! - client.Dispose(); + await Task.Delay(500); - await Task.Delay(1000); + Assert.IsNotNull(ex); + Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException)); + Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException)); + } - Assert.AreEqual(1, i); - } - } + [TestMethod] + public async Task Ensure_Queue_Drain() + { + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectLowLevelClient(); - [TestMethod] - public async Task Fire_Disconnected_Event_On_Server_Shutdown() + var i = 0; + server.InterceptingPublishAsync += _ => { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + i++; + return CompletedTask.Instance; + }; - var handlerFired = false; - client.DisconnectedAsync += e => - { - handlerFired = true; - return CompletedTask.Instance; - }; + await client.SendAsync( + new MqttConnectPacket + { + ClientId = "Ensure_Queue_Drain_Test" + }, + CancellationToken.None); - await server.StopAsync(); + await client.SendAsync( + new MqttPublishPacket + { + Topic = "Test" + }, + CancellationToken.None); - await Task.Delay(4000); + await Task.Delay(500); - Assert.IsTrue(handlerFired); - } - } + // This will simulate a device which closes the connection directly + // after sending the data so do delay is added between send and dispose! + client.Dispose(); - [TestMethod] - public async Task Frequent_Connects() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + await Task.Delay(1000); - var clients = new List(); - for (var i = 0; i < 100; i++) - { - clients.Add(await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a"))); - } + Assert.AreEqual(1, i); + } - await Task.Delay(500); + [TestMethod] + public async Task Fire_Disconnected_Event_On_Server_Shutdown() + { + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - var clientStatus = await testEnvironment.Server.GetClientsAsync(); - var sessionStatus = await testEnvironment.Server.GetSessionsAsync(); + var handlerFired = false; + client.DisconnectedAsync += _ => + { + handlerFired = true; + return CompletedTask.Instance; + }; - for (var i = 0; i < 98; i++) - { - Assert.IsFalse(clients[i].TryPingAsync().GetAwaiter().GetResult(), $"clients[{i}] is not connected"); - } + await server.StopAsync(); - Assert.IsTrue(clients[99].TryPingAsync().GetAwaiter().GetResult()); + await Task.Delay(4000); - Assert.AreEqual(1, clientStatus.Count); - Assert.AreEqual(1, sessionStatus.Count); + Assert.IsTrue(handlerFired); + } - var receiveClient = clients[99]; - object receivedPayload = null; - receiveClient.ApplicationMessageReceivedAsync += e => - { - receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); - return CompletedTask.Instance; - }; + [TestMethod] + public async Task Frequent_Connects() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - await receiveClient.SubscribeAsync("x"); + var clients = new List(); + for (var i = 0; i < 100; i++) + { + clients.Add(await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a"))); + } - var sendClient = await testEnvironment.ConnectClient(); - await sendClient.PublishStringAsync("x", "1"); + await Task.Delay(500); - await Task.Delay(250); + var clientStatus = await testEnvironment.Server.GetClientsAsync(); + var sessionStatus = await testEnvironment.Server.GetSessionsAsync(); - Assert.AreEqual("1", receivedPayload); - } + for (var i = 0; i < 98; i++) + { + Assert.IsFalse(clients[i].TryPingAsync().GetAwaiter().GetResult(), $"clients[{i}] is not connected"); } - [TestMethod] - public async Task Invalid_Connect_Throws_Exception() - { - var factory = new MqttClientFactory(); - using (var client = factory.CreateMqttClient()) - { - try - { - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); + Assert.IsTrue(clients[99].TryPingAsync().GetAwaiter().GetResult()); - Assert.Fail("Must fail!"); - } - catch (Exception exception) - { - Assert.IsNotNull(exception); - Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException)); - Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException)); - } - } - } + Assert.AreEqual(1, clientStatus.Count); + Assert.AreEqual(1, sessionStatus.Count); - [TestMethod] - public async Task No_Payload() + var receiveClient = clients[99]; + object receivedPayload = null; + receiveClient.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); + return CompletedTask.Instance; + }; - var sender = await testEnvironment.ConnectClient(); - var receiver = await testEnvironment.ConnectClient(); + await receiveClient.SubscribeAsync("x"); - var message = new MqttApplicationMessageBuilder().WithTopic("A"); + var sendClient = await testEnvironment.ConnectClient(); + await sendClient.PublishStringAsync("x", "1"); - await receiver.SubscribeAsync( - new MqttClientSubscribeOptions - { - TopicFilters = new List { new MqttTopicFilter { Topic = "#" } } - }, - CancellationToken.None); + await Task.Delay(250); - MqttApplicationMessage receivedMessage = null; - receiver.ApplicationMessageReceivedAsync += e => - { - receivedMessage = e.ApplicationMessage; - return CompletedTask.Instance; - }; - - await sender.PublishAsync(message.Build(), CancellationToken.None); + Assert.AreEqual("1", receivedPayload); + } - await Task.Delay(1000); + [TestMethod] + public async Task Invalid_Connect_Throws_Exception() + { + var factory = new MqttClientFactory(); + using var client = factory.CreateMqttClient(); + try + { + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build()); - Assert.IsNotNull(receivedMessage); - Assert.AreEqual("A", receivedMessage.Topic); - Assert.AreEqual(0, receivedMessage.Payload.Length); - } + Assert.Fail("Must fail!"); } - - [TestMethod] - public async Task NoConnectedHandler_Connect_DoesNotThrowException() + catch (Exception exception) { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + Assert.IsNotNull(exception); + Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException)); + Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException)); + } + } - var client = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task No_Payload() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); - } - } + var sender = await testEnvironment.ConnectClient(); + var receiver = await testEnvironment.ConnectClient(); - [TestMethod] - public async Task NoDisconnectedHandler_Disconnect_DoesNotThrowException() - { - using (var testEnvironment = new TestEnvironment(TestContext)) + var message = new MqttApplicationMessageBuilder().WithTopic("A"); + + await receiver.SubscribeAsync( + new MqttClientSubscribeOptions { - await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); - Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); + TopicFilters = + [ + new() { Topic = "#" } + ] + }, + CancellationToken.None); + + MqttApplicationMessage receivedMessage = null; + receiver.ApplicationMessageReceivedAsync += e => + { + receivedMessage = e.ApplicationMessage; + return CompletedTask.Instance; + }; - await client.DisconnectAsync(); + await sender.PublishAsync(message.Build(), CancellationToken.None); - Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult()); - } - } + await Task.Delay(1000); - [TestMethod] - public async Task PacketIdentifier_In_Publish_Result() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + Assert.IsNotNull(receivedMessage); + Assert.AreEqual("A", receivedMessage.Topic); + Assert.AreEqual(0, receivedMessage.Payload.Length); + } - var result = await client.PublishStringAsync("a", "a"); - Assert.AreEqual(null, result.PacketIdentifier); + [TestMethod] + public async Task NoConnectedHandler_Connect_DoesNotThrowException() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - result = await client.PublishStringAsync("b", "b"); - Assert.AreEqual(null, result.PacketIdentifier); + var client = await testEnvironment.ConnectClient(); - result = await client.PublishStringAsync("a", "a", MqttQualityOfServiceLevel.AtLeastOnce); - Assert.AreEqual((ushort)1, result.PacketIdentifier); + Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); + } - result = await client.PublishStringAsync("b", "b", MqttQualityOfServiceLevel.AtLeastOnce); - Assert.AreEqual((ushort)2, result.PacketIdentifier); + [TestMethod] + public async Task NoDisconnectedHandler_Disconnect_DoesNotThrowException() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); + Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); - result = await client.PublishStringAsync("a", "a", MqttQualityOfServiceLevel.ExactlyOnce); - Assert.AreEqual((ushort)3, result.PacketIdentifier); + await client.DisconnectAsync(); - result = await client.PublishStringAsync("b", "b", MqttQualityOfServiceLevel.ExactlyOnce); - Assert.AreEqual((ushort)4, result.PacketIdentifier); - } - } + Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult()); + } - [TestMethod] - public async Task Preserve_Message_Order() - { - // The messages are sent in reverse or to ensure that the delay in the handler - // needs longer for the first messages and later messages may be processed earlier (if there - // is an issue). - const int MessagesCount = 50; + [TestMethod] + public async Task PacketIdentifier_In_Publish_Result() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + var result = await client.PublishStringAsync("a", "a"); + Assert.AreEqual(null, result.PacketIdentifier); - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("x"); + result = await client.PublishStringAsync("b", "b"); + Assert.AreEqual(null, result.PacketIdentifier); - var receivedValues = new List(); + result = await client.PublishStringAsync("a", "a", MqttQualityOfServiceLevel.AtLeastOnce); + Assert.AreEqual((ushort)1, result.PacketIdentifier); - async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) - { - var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString()); - await Task.Delay(value); + result = await client.PublishStringAsync("b", "b", MqttQualityOfServiceLevel.AtLeastOnce); + Assert.AreEqual((ushort)2, result.PacketIdentifier); - lock (receivedValues) - { - receivedValues.Add(value); - } - } + result = await client.PublishStringAsync("a", "a", MqttQualityOfServiceLevel.ExactlyOnce); + Assert.AreEqual((ushort)3, result.PacketIdentifier); - client1.ApplicationMessageReceivedAsync += Handler1; + result = await client.PublishStringAsync("b", "b", MqttQualityOfServiceLevel.ExactlyOnce); + Assert.AreEqual((ushort)4, result.PacketIdentifier); + } - var client2 = await testEnvironment.ConnectClient(); - for (var i = MessagesCount; i > 0; i--) - { - await client2.PublishStringAsync("x", i.ToString()); - } + [TestMethod] + public async Task Preserve_Message_Order() + { + // The messages are sent in reverse or to ensure that the delay in the handler + // needs longer for the first messages and later messages may be processed earlier (if there + // is an issue). + const int MessagesCount = 50; - await Task.Delay(5000); + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - for (var i = MessagesCount; i > 0; i--) - { - Assert.AreEqual(i, receivedValues[MessagesCount - i]); - } + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("x"); + + var receivedValues = new List(); + + async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) + { + var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString()); + await Task.Delay(value); + + lock (receivedValues) + { + receivedValues.Add(value); } } - [TestMethod] - public async Task Preserve_Message_Order_With_Delayed_Acknowledgement() + client1.ApplicationMessageReceivedAsync += Handler1; + + var client2 = await testEnvironment.ConnectClient(); + for (var i = MessagesCount; i > 0; i--) { - // The messages are sent in reverse or to ensure that the delay in the handler - // needs longer for the first messages and later messages may be processed earlier (if there - // is an issue). - const int MessagesCount = 50; + await client2.PublishStringAsync("x", i.ToString()); + } - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + await Task.Delay(5000); - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.ExactlyOnce); + for (var i = MessagesCount; i > 0; i--) + { + Assert.AreEqual(i, receivedValues[MessagesCount - i]); + } + } - var receivedValues = new List(); + [TestMethod] + public async Task Preserve_Message_Order_With_Delayed_Acknowledgement() + { + // The messages are sent in reverse or to ensure that the delay in the handler + // needs longer for the first messages and later messages may be processed earlier (if there + // is an issue). + const int MessagesCount = 50; - Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) - { - var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString()); - eventArgs.AutoAcknowledge = false; - Task.Delay(value).ContinueWith(x => eventArgs.AcknowledgeAsync(CancellationToken.None)); + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - Debug.WriteLine($"received {value}"); - lock (receivedValues) - { - receivedValues.Add(value); - } + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.ExactlyOnce); - return CompletedTask.Instance; - } + var receivedValues = new List(); - client1.ApplicationMessageReceivedAsync += Handler1; + client1.ApplicationMessageReceivedAsync += Handler1; - var client2 = await testEnvironment.ConnectClient(); - for (var i = MessagesCount; i > 0; i--) - { - await client2.PublishStringAsync("x", i.ToString(), MqttQualityOfServiceLevel.ExactlyOnce); - } + var client2 = await testEnvironment.ConnectClient(); + for (var i = MessagesCount; i > 0; i--) + { + await client2.PublishStringAsync("x", i.ToString(), MqttQualityOfServiceLevel.ExactlyOnce); + } - await Task.Delay(5000); + await Task.Delay(5000); - for (var i = MessagesCount; i > 0; i--) - { - Assert.AreEqual(i, receivedValues[MessagesCount - i]); - } - } + for (var i = MessagesCount; i > 0; i--) + { + Assert.AreEqual(i, receivedValues[MessagesCount - i]); } - [TestMethod] - public async Task Publish_QoS_0_Over_Period_Exceeding_KeepAlive() + return; + + Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) { - using (var testEnvironment = new TestEnvironment(TestContext)) + var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString()); + eventArgs.AutoAcknowledge = false; + Task.Delay(value).ContinueWith(_ => eventArgs.AcknowledgeAsync(CancellationToken.None)); + + Debug.WriteLine($"received {value}"); + lock (receivedValues) { - const int KeepAlivePeriodSecs = 3; + receivedValues.Add(value); + } + + return CompletedTask.Instance; + } + } - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_QoS_0_Over_Period_Exceeding_KeepAlive() + { + using var testEnvironment = new TestEnvironment(TestContext); + const int KeepAlivePeriodSecs = 3; - var options = new MqttClientOptionsBuilder().WithKeepAlivePeriod(TimeSpan.FromSeconds(KeepAlivePeriodSecs)); - var client = await testEnvironment.ConnectClient(options); - var message = new MqttApplicationMessageBuilder().WithTopic("a").Build(); + await testEnvironment.StartServer(); - try - { - // Publish messages over a time period exceeding the keep alive period. - // This should not cause an exception because of, i.e. "Client disconnected". - - for (var count = 0; count < KeepAlivePeriodSecs * 3; ++count) - { - // Send Publish requests well before the keep alive period expires - await client.PublishAsync(message); - await Task.Delay(1000); - } - } - catch (Exception ex) - { - Assert.Fail(ex.Message); - } + var options = new MqttClientOptionsBuilder().WithKeepAlivePeriod(TimeSpan.FromSeconds(KeepAlivePeriodSecs)); + var client = await testEnvironment.ConnectClient(options); + var message = new MqttApplicationMessageBuilder().WithTopic("a").Build(); + + try + { + // Publish messages over a time period exceeding the keep alive period. + // This should not cause an exception because of, i.e. "Client disconnected". + + for (var count = 0; count < KeepAlivePeriodSecs * 3; ++count) + { + // Send Publish requests well before the keep alive period expires + await client.PublishAsync(message); + await Task.Delay(1000); } } + catch (Exception ex) + { + Assert.Fail(ex.Message); + } + } + + [TestMethod] + public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + + const string client1Topic = "client1/topic"; + const string client2Topic = "client2/topic"; + const string expectedClient2Message = "hello client2"; - [TestMethod] - public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler() + var client1 = await testEnvironment.ConnectClient(); + client1.ApplicationMessageReceivedAsync += async _ => { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + await client1.PublishStringAsync(client2Topic, expectedClient2Message, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false); + }; - const string client1Topic = "client1/topic"; - const string client2Topic = "client2/topic"; - const string expectedClient2Message = "hello client2"; + await client1.SubscribeAsync(client1Topic, MqttQualityOfServiceLevel.AtLeastOnce); - var client1 = await testEnvironment.ConnectClient(); - client1.ApplicationMessageReceivedAsync += async e => - { - await client1.PublishStringAsync(client2Topic, expectedClient2Message, MqttQualityOfServiceLevel.AtLeastOnce); - }; + var client2 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync(client1Topic, MqttQualityOfServiceLevel.AtLeastOnce); + var client2TopicResults = new List(); - var client2 = await testEnvironment.ConnectClient(); + client2.ApplicationMessageReceivedAsync += e => + { + client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload.ToArray())); + return CompletedTask.Instance; + }; - var client2TopicResults = new List(); + await client2.SubscribeAsync(client2Topic); - client2.ApplicationMessageReceivedAsync += e => - { - client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload.ToArray())); - return CompletedTask.Instance; - }; + var client3 = await testEnvironment.ConnectClient(); + var message = new MqttApplicationMessageBuilder().WithTopic(client1Topic).Build(); + await client3.PublishAsync(message); + await client3.PublishAsync(message); - await client2.SubscribeAsync(client2Topic); + await Task.Delay(500); - var client3 = await testEnvironment.ConnectClient(); - var message = new MqttApplicationMessageBuilder().WithTopic(client1Topic).Build(); - await client3.PublishAsync(message); - await client3.PublishAsync(message); + Assert.AreEqual(2, client2TopicResults.Count); + Assert.AreEqual(expectedClient2Message, client2TopicResults[0]); + Assert.AreEqual(expectedClient2Message, client2TopicResults[1]); + } - await Task.Delay(500); + [TestMethod] + public async Task Publish_With_Correct_Retain_Flag() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - Assert.AreEqual(2, client2TopicResults.Count); - Assert.AreEqual(expectedClient2Message, client2TopicResults[0]); - Assert.AreEqual(expectedClient2Message, client2TopicResults[1]); - } - } + var receivedMessages = new List(); - [TestMethod] - public async Task Publish_With_Correct_Retain_Flag() + var client1 = await testEnvironment.ConnectClient(); + client1.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = new TestEnvironment(TestContext)) + lock (receivedMessages) { - await testEnvironment.StartServer(); + receivedMessages.Add(e.ApplicationMessage); + } - var receivedMessages = new List(); + return CompletedTask.Instance; + }; - var client1 = await testEnvironment.ConnectClient(); - client1.ApplicationMessageReceivedAsync += e => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } + await client1.SubscribeAsync("a"); - return CompletedTask.Instance; - }; + var client2 = await testEnvironment.ConnectClient(); + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithRetainFlag().Build(); + await client2.PublishAsync(message); - await client1.SubscribeAsync("a"); + await Task.Delay(500); - var client2 = await testEnvironment.ConnectClient(); - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithRetainFlag().Build(); - await client2.PublishAsync(message); + Assert.AreEqual(1, receivedMessages.Count); + Assert.IsFalse(receivedMessages[0].Retain); // Must be false even if set above! + } - await Task.Delay(500); + [TestMethod] + public async Task Reconnect() + { + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - Assert.AreEqual(1, receivedMessages.Count); - Assert.IsFalse(receivedMessages.First().Retain); // Must be false even if set above! - } - } + await Task.Delay(500); + Assert.IsTrue(await client.TryPingAsync()); - [TestMethod] - public async Task Reconnect() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + await server.StopAsync(); + await Task.Delay(500); + Assert.IsFalse(await client.TryPingAsync()); + + await server.StartAsync(); + await Task.Delay(500); - await Task.Delay(500); - Assert.IsTrue(await client.TryPingAsync()); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + Assert.IsTrue(await client.TryPingAsync()); + } - await server.StopAsync(); - await Task.Delay(500); - Assert.IsFalse(await client.TryPingAsync()); + [TestMethod] + public async Task Reconnect_From_Disconnected_Event() + { + using var testEnvironment = new TestEnvironment(TestContext); + testEnvironment.IgnoreClientLogErrors = true; - await server.StartAsync(); - await Task.Delay(500); + var client = testEnvironment.CreateClient(); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - Assert.IsTrue(await client.TryPingAsync()); - } - } + var tries = 0; + var maxTries = 3; - [TestMethod] - public async Task Reconnect_From_Disconnected_Event() + client.DisconnectedAsync += async _ => { - using (var testEnvironment = new TestEnvironment(TestContext)) + if (tries >= maxTries) { - testEnvironment.IgnoreClientLogErrors = true; + return; + } - var client = testEnvironment.CreateClient(); + Interlocked.Increment(ref tries); - var tries = 0; - var maxTries = 3; + await Task.Delay(100); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()).ConfigureAwait(false); + }; - client.DisconnectedAsync += async e => - { - if (tries >= maxTries) - { - return; - } + try + { + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + Assert.Fail("Must fail!"); + } + catch + { + // Ignore errors. + } - Interlocked.Increment(ref tries); + SpinWait.SpinUntil(() => tries >= maxTries, 10000); - await Task.Delay(100); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - }; + Assert.AreEqual(maxTries, tries); + } - try - { - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - Assert.Fail("Must fail!"); - } - catch - { - // Ignore errors. - } + [TestMethod] + public async Task Reconnect_While_Server_Offline() + { + using var testEnvironment = new TestEnvironment(TestContext); + testEnvironment.IgnoreClientLogErrors = true; - SpinWait.SpinUntil(() => tries >= maxTries, 10000); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - Assert.AreEqual(maxTries, tries); - } - } + await Task.Delay(500); + Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); + + await server.StopAsync(); + await Task.Delay(500); + Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult()); - [TestMethod] - public async Task Reconnect_While_Server_Offline() + for (var i = 0; i < 5; i++) { - using (var testEnvironment = new TestEnvironment(TestContext)) + try + { + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + Assert.Fail("Must fail!"); + } + catch { - testEnvironment.IgnoreClientLogErrors = true; + // Ignore errors. + } + } - var server = await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + await server.StartAsync(); + await Task.Delay(500); - await Task.Delay(500); - Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + await client.PingAsync(); + } - await server.StopAsync(); - await Task.Delay(500); - Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult()); + [TestMethod] + public async Task Send_Manual_Ping() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - for (var i = 0; i < 5; i++) - { - try - { - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - Assert.Fail("Must fail!"); - } - catch - { - // Ignore errors. - } - } + await client.PingAsync(CancellationToken.None); + } - await server.StartAsync(); - await Task.Delay(500); + [TestMethod] + public async Task Send_Reply_For_Any_Received_Message() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - await client.PingAsync(); - } - } + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("request/+"); - [TestMethod] - public async Task Send_Manual_Ping() + async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); - - await client.PingAsync(CancellationToken.None); - } + await client1.PublishStringAsync($"reply/{eventArgs.ApplicationMessage.Topic}"); } - [TestMethod] - public async Task Send_Reply_For_Any_Received_Message() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + client1.ApplicationMessageReceivedAsync += Handler1; - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("request/+"); + var client2 = await testEnvironment.ConnectClient(); + await client2.SubscribeAsync("reply/#"); - async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) - { - await client1.PublishStringAsync($"reply/{eventArgs.ApplicationMessage.Topic}"); - } + var replies = new List(); - client1.ApplicationMessageReceivedAsync += Handler1; + Task Handler2(MqttApplicationMessageReceivedEventArgs eventArgs) + { + lock (replies) + { + replies.Add(eventArgs.ApplicationMessage.Topic); + } - var client2 = await testEnvironment.ConnectClient(); - await client2.SubscribeAsync("reply/#"); + return CompletedTask.Instance; + } - var replies = new List(); + client2.ApplicationMessageReceivedAsync += Handler2; - Task Handler2(MqttApplicationMessageReceivedEventArgs eventArgs) - { - lock (replies) - { - replies.Add(eventArgs.ApplicationMessage.Topic); - } + await Task.Delay(500); - return CompletedTask.Instance; - } + await client2.PublishStringAsync("request/a"); + await client2.PublishStringAsync("request/b"); + await client2.PublishStringAsync("request/c"); - client2.ApplicationMessageReceivedAsync += Handler2; + await Task.Delay(500); - await Task.Delay(500); + Assert.AreEqual("reply/request/a,reply/request/b,reply/request/c", string.Join(",", replies)); + } - await client2.PublishStringAsync("request/a"); - await client2.PublishStringAsync("request/b"); - await client2.PublishStringAsync("request/c"); + [TestMethod] + public async Task Send_Reply_In_Message_Handler() + { + using var testEnvironment = new TestEnvironment(); + await testEnvironment.StartServer(); + var client1 = await testEnvironment.ConnectClient(); + var client2 = await testEnvironment.ConnectClient(); - await Task.Delay(500); + await client1.SubscribeAsync("#"); + await client2.SubscribeAsync("#"); - Assert.AreEqual("reply/request/a,reply/request/b,reply/request/c", string.Join(",", replies)); - } - } + var replyReceived = false; - [TestMethod] - public async Task Send_Reply_In_Message_Handler() + client1.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = new TestEnvironment()) + if (e.ApplicationMessage.Topic == "reply") { - await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClient(); - var client2 = await testEnvironment.ConnectClient(); + replyReceived = true; + } - await client1.SubscribeAsync("#"); - await client2.SubscribeAsync("#"); + return CompletedTask.Instance; + }; - var replyReceived = false; + client2.ApplicationMessageReceivedAsync += async e => + { + if (e.ApplicationMessage.Topic == "request") + { + // Use AtMostOnce here because with QoS 1 or even QoS 2 the process waits for + // the ACK etc. The problem is that the SpinUntil below only waits until the + // flag is set. It does not wait until the client has sent the ACK + await client2.PublishStringAsync("reply"); + } + }; - client1.ApplicationMessageReceivedAsync += e => - { - if (e.ApplicationMessage.Topic == "reply") - { - replyReceived = true; - } + await client1.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce); - return CompletedTask.Instance; - }; + await Task.Delay(500); - client2.ApplicationMessageReceivedAsync += async e => - { - if (e.ApplicationMessage.Topic == "request") - { - // Use AtMostOnce here because with QoS 1 or even QoS 2 the process waits for - // the ACK etc. The problem is that the SpinUntil below only waits until the - // flag is set. It does not wait until the client has sent the ACK - await client2.PublishStringAsync("reply"); - } - }; + SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10)); - await client1.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce); + await Task.Delay(500); - await Task.Delay(500); + Assert.IsTrue(replyReceived); + } - SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10)); + [TestMethod] + public async Task Send_Reply_In_Message_Handler_For_Same_Client() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - await Task.Delay(500); + await client.SubscribeAsync("#"); - Assert.IsTrue(replyReceived); - } - } + var replyReceived = false; - [TestMethod] - public async Task Send_Reply_In_Message_Handler_For_Same_Client() + client.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = new TestEnvironment(TestContext)) + if (e.ApplicationMessage.Topic == "request") { - await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); - - await client.SubscribeAsync("#"); - - var replyReceived = false; - - client.ApplicationMessageReceivedAsync += e => - { - if (e.ApplicationMessage.Topic == "request") - { #pragma warning disable 4014 - Task.Run(() => client.PublishStringAsync("reply", null, MqttQualityOfServiceLevel.AtLeastOnce)); + Task.Run(() => client.PublishStringAsync("reply", null, MqttQualityOfServiceLevel.AtLeastOnce)); #pragma warning restore 4014 - } - else - { - replyReceived = true; - } + } + else + { + replyReceived = true; + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce); + await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce); - SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10)); + SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10)); - Assert.IsTrue(replyReceived); - } - } + Assert.IsTrue(replyReceived); + } + + [TestMethod] + public async Task Set_ClientWasConnected_On_ClientDisconnect() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - [TestMethod] - public async Task Set_ClientWasConnected_On_ClientDisconnect() + Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); + client.DisconnectedAsync += e => { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + Assert.IsTrue(e.ClientWasConnected); + return CompletedTask.Instance; + }; - Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); - client.DisconnectedAsync += e => - { - Assert.IsTrue(e.ClientWasConnected); - return CompletedTask.Instance; - }; + await client.DisconnectAsync(); + await Task.Delay(200); + } - await client.DisconnectAsync(); - await Task.Delay(200); - } - } + [TestMethod] + public async Task Set_ClientWasConnected_On_ServerDisconnect() + { + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - [TestMethod] - public async Task Set_ClientWasConnected_On_ServerDisconnect() + Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); + client.DisconnectedAsync += e => { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + Assert.IsTrue(e.ClientWasConnected); + return CompletedTask.Instance; + }; - Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult()); - client.DisconnectedAsync += e => - { - Assert.IsTrue(e.ClientWasConnected); - return CompletedTask.Instance; - }; + await server.StopAsync(); + await Task.Delay(4000); + } - await server.StopAsync(); - await Task.Delay(4000); - } - } + [TestMethod] + public async Task Subscribe_In_Callback_Events() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - [TestMethod] - public async Task Subscribe_In_Callback_Events() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); + var receivedMessages = new List(); - var receivedMessages = new List(); + var client = testEnvironment.CreateClient(); - var client = testEnvironment.CreateClient(); + client.ConnectedAsync += async e => + { + await client.SubscribeAsync("RCU/P1/H0001/R0003"); - client.ConnectedAsync += async e => - { - await client.SubscribeAsync("RCU/P1/H0001/R0003"); + var msg = new MqttApplicationMessageBuilder().WithPayload("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|") + .WithTopic("RCU/P1/H0001/R0003"); - var msg = new MqttApplicationMessageBuilder().WithPayload("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|") - .WithTopic("RCU/P1/H0001/R0003"); + await client.PublishAsync(msg.Build()); + }; - await client.PublishAsync(msg.Build()); - }; + client.ApplicationMessageReceivedAsync += e => + { + lock (receivedMessages) + { + receivedMessages.Add(e.ApplicationMessage); + } - client.ApplicationMessageReceivedAsync += e => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build()); - await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build()); + await Task.Delay(500); - await Task.Delay(500); + Assert.AreEqual(1, receivedMessages.Count); + Assert.AreEqual("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|", receivedMessages.First().ConvertPayloadToString()); + } - Assert.AreEqual(1, receivedMessages.Count); - Assert.AreEqual("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|", receivedMessages.First().ConvertPayloadToString()); - } - } + [TestMethod] + public async Task Subscribe_With_QoS2() + { + using var testEnvironment = new TestEnvironment(); + await testEnvironment.StartServer(); + var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - [TestMethod] - public async Task Subscribe_With_QoS2() + var disconnectedFired = false; + client1.DisconnectedAsync += c => { - using (var testEnvironment = new TestEnvironment()) - { - await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - - var disconnectedFired = false; - client1.DisconnectedAsync += c => - { - disconnectedFired = true; - return CompletedTask.Instance; - }; + disconnectedFired = true; + return CompletedTask.Instance; + }; - var messageReceived = false; - client1.ApplicationMessageReceivedAsync += e => - { - messageReceived = true; - return CompletedTask.Instance; - }; + var messageReceived = false; + client1.ApplicationMessageReceivedAsync += _ => + { + messageReceived = true; + return CompletedTask.Instance; + }; - await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build()); + await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build()); - await Task.Delay(500); + await Task.Delay(500); - var message = new MqttApplicationMessageBuilder().WithTopic("topic1") - .WithPayload("Hello World") - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) - .WithRetainFlag() - .Build(); + var message = new MqttApplicationMessageBuilder().WithTopic("topic1") + .WithPayload("Hello World") + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) + .WithRetainFlag() + .Build(); - await client2.PublishAsync(message); - await Task.Delay(500); + await client2.PublishAsync(message); + await Task.Delay(500); - Assert.IsTrue(messageReceived); - Assert.IsTrue(client1.TryPingAsync().GetAwaiter().GetResult()); - Assert.IsFalse(disconnectedFired); - } - } + Assert.IsTrue(messageReceived); + Assert.IsTrue(client1.TryPingAsync().GetAwaiter().GetResult()); + Assert.IsFalse(disconnectedFired); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs index 65e03f7d1..c6fab6287 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs @@ -3,23 +3,22 @@ // See the LICENSE file in the project root for more information. using System.Linq; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace MQTTnet.Tests.Clients +namespace MQTTnet.Tests.Clients; + +// ReSharper disable InconsistentNaming +[TestClass] +public class MqttClientOptionsBuilder_Tests { - [TestClass] - public class MqttClientOptionsBuilder_Tests + [TestMethod] + public void WithConnectionUri_Credential_Test() { - [TestMethod] - public void WithConnectionUri_Credential_Test() - { - var options = new MqttClientOptionsBuilder() - .WithConnectionUri("mqtt://user:password@127.0.0.1") - .Build(); + var options = new MqttClientOptionsBuilder() + .WithConnectionUri("mqtt://user:password@127.0.0.1") + .Build(); - Assert.AreEqual("user", options.Credentials.GetUserName(null)); - Assert.IsTrue(Encoding.UTF8.GetBytes("password").SequenceEqual(options.Credentials.GetPassword(null))); - } + Assert.AreEqual("user", options.Credentials.GetUserName(null)); + Assert.IsTrue("password"u8.ToArray().SequenceEqual(options.Credentials.GetPassword(null))); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Diagnostics/Logger_Tests.cs b/Source/MQTTnet.Tests/Diagnostics/Logger_Tests.cs index 8b2c5d1e5..636a8a061 100644 --- a/Source/MQTTnet.Tests/Diagnostics/Logger_Tests.cs +++ b/Source/MQTTnet.Tests/Diagnostics/Logger_Tests.cs @@ -6,61 +6,61 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Tests.Diagnostics +namespace MQTTnet.Tests.Diagnostics; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Logger_Tests : BaseTestClass { - [TestClass] - public sealed class Logger_Tests : BaseTestClass + [TestMethod] + public void Log_Without_Source() { - [TestMethod] - public void Log_Without_Source() - { - var logger = new MqttNetEventLogger(); + var logger = new MqttNetEventLogger(); - MqttNetLogMessage logMessage = null; - logger.LogMessagePublished += (s, e) => { logMessage = e.LogMessage; }; + MqttNetLogMessage logMessage = null; + logger.LogMessagePublished += (_, e) => { logMessage = e.LogMessage; }; - logger.Publish(MqttNetLogLevel.Info, "SOURCE", "MESSAGE", new object[] { "ABC" }, new InvalidOperationException()); + logger.Publish(MqttNetLogLevel.Info, "SOURCE", "MESSAGE", ["ABC"], new InvalidOperationException()); - Assert.AreEqual(MqttNetLogLevel.Info, logMessage.Level); - Assert.AreEqual("SOURCE", logMessage.Source); - Assert.AreEqual("MESSAGE", logMessage.Message); - Assert.AreEqual("InvalidOperationException", logMessage.Exception.GetType().Name); - } + Assert.AreEqual(MqttNetLogLevel.Info, logMessage.Level); + Assert.AreEqual("SOURCE", logMessage.Source); + Assert.AreEqual("MESSAGE", logMessage.Message); + Assert.AreEqual("InvalidOperationException", logMessage.Exception.GetType().Name); + } - [TestMethod] - public void Root_Log_Messages() - { - var logger = new MqttNetEventLogger(); - var childLogger = logger.WithSource("Source1"); + [TestMethod] + public void Root_Log_Messages() + { + var logger = new MqttNetEventLogger(); + var childLogger = logger.WithSource("Source1"); - var logMessagesCount = 0; + var logMessagesCount = 0; - logger.LogMessagePublished += (s, e) => { logMessagesCount++; }; + logger.LogMessagePublished += (_, _) => { logMessagesCount++; }; - childLogger.Verbose("Verbose"); - childLogger.Info("Info"); - childLogger.Warning((Exception)null, "Warning"); - childLogger.Error(null, "Error"); + childLogger.Verbose("Verbose"); + childLogger.Info("Info"); + childLogger.Warning((Exception)null, "Warning"); + childLogger.Error(null, "Error"); - Assert.AreEqual(4, logMessagesCount); - } + Assert.AreEqual(4, logMessagesCount); + } - [TestMethod] - public void Use_Custom_Log_Id() - { - var logger = new MqttNetEventLogger("logId"); - var childLogger = logger.WithSource("Source1"); + [TestMethod] + public void Use_Custom_Log_Id() + { + var logger = new MqttNetEventLogger("logId"); + var childLogger = logger.WithSource("Source1"); - logger.LogMessagePublished += (s, e) => - { - Assert.AreEqual("logId", e.LogMessage.LogId); - Assert.AreEqual("Source1", e.LogMessage.Source); - }; + logger.LogMessagePublished += (_, e) => + { + Assert.AreEqual("logId", e.LogMessage.LogId); + Assert.AreEqual("Source1", e.LogMessage.Source); + }; - childLogger.Verbose("Verbose"); - childLogger.Info("Info"); - childLogger.Warning((Exception)null, "Warning"); - childLogger.Error(null, "Error"); - } + childLogger.Verbose("Verbose"); + childLogger.Info("Info"); + childLogger.Warning((Exception)null, "Warning"); + childLogger.Error(null, "Error"); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs b/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs index 7db513f58..0bc6ea6d4 100644 --- a/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs +++ b/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs @@ -10,41 +10,37 @@ using MQTTnet.Formatter; using MQTTnet.Internal; -namespace MQTTnet.Tests.Diagnostics +namespace MQTTnet.Tests.Diagnostics; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class PacketInspection_Tests : BaseTestClass { - [TestClass] - public sealed class PacketInspection_Tests : BaseTestClass + [TestMethod] + public async Task Inspect_Client_Packets() { - [TestMethod] - public async Task Inspect_Client_Packets() + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + using var mqttClient = testEnvironment.CreateClient(); + var mqttClientOptions = testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithClientId("CLIENT_ID") // Must be fixed. + .WithProtocolVersion(MqttProtocolVersion.V311) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .Build(); + + var packets = new List(); + + mqttClient.InspectPacketAsync += eventArgs => { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - using (var mqttClient = testEnvironment.CreateClient()) - { - var mqttClientOptions = testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithClientId("CLIENT_ID") // Must be fixed. - .WithProtocolVersion(MqttProtocolVersion.V311) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .Build(); - - var packets = new List(); - - mqttClient.InspectPacketAsync += eventArgs => - { - packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); - return CompletedTask.Instance; - }; - - await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - - Assert.AreEqual(2, packets.Count); - Assert.AreEqual("Outbound:ECwABE1RVFQEAgAPACBJbnNwZWN0X0NsaWVudF9QYWNrZXRzX0NMSUVOVF9JRA==", packets[0]); // CONNECT - Assert.AreEqual("Inbound:IAIAAA==", packets[1]); // CONNACK - } - } - } + packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); + return CompletedTask.Instance; + }; + + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + + Assert.AreEqual(2, packets.Count); + Assert.AreEqual("Outbound:ECwABE1RVFQEAgAPACBJbnNwZWN0X0NsaWVudF9QYWNrZXRzX0NMSUVOVF9JRA==", packets[0]); // CONNECT + Assert.AreEqual("Inbound:IAIAAA==", packets[1]); // CONNACK } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Diagnostics/SourceLogger_Tests.cs b/Source/MQTTnet.Tests/Diagnostics/SourceLogger_Tests.cs index 3e3c411c5..8adf37658 100644 --- a/Source/MQTTnet.Tests/Diagnostics/SourceLogger_Tests.cs +++ b/Source/MQTTnet.Tests/Diagnostics/SourceLogger_Tests.cs @@ -3,29 +3,28 @@ // See the LICENSE file in the project root for more information. using Microsoft.VisualStudio.TestTools.UnitTesting; - using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Tests.Diagnostics +namespace MQTTnet.Tests.Diagnostics; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class SourceLogger_Tests : BaseTestClass { - [TestClass] - public sealed class SourceLogger_Tests : BaseTestClass + [TestMethod] + public void Log_With_Source() { - [TestMethod] - public void Log_With_Source() - { - MqttNetLogMessage logMessage = null; + MqttNetLogMessage logMessage = null; - var logger = new MqttNetEventLogger(); - logger.LogMessagePublished += (s, e) => - { - logMessage = e.LogMessage; - }; + var logger = new MqttNetEventLogger(); + logger.LogMessagePublished += (_, e) => + { + logMessage = e.LogMessage; + }; - var sourceLogger = logger.WithSource("The_Source"); - sourceLogger.Info("MESSAGE", (object)null, (object)null); + var sourceLogger = logger.WithSource("The_Source"); + sourceLogger.Info("MESSAGE", (object)null, (object)null); - Assert.AreEqual("The_Source", logMessage.Source); - } + Assert.AreEqual("The_Source", logMessage.Source); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Extensions/MqttTopicTemplate_Tests.cs b/Source/MQTTnet.Tests/Extensions/MqttTopicTemplate_Tests.cs index e3c7ad201..e261f7634 100644 --- a/Source/MQTTnet.Tests/Extensions/MqttTopicTemplate_Tests.cs +++ b/Source/MQTTnet.Tests/Extensions/MqttTopicTemplate_Tests.cs @@ -8,266 +8,254 @@ using MQTTnet.Exceptions; using MQTTnet.Extensions.TopicTemplate; -namespace MQTTnet.Tests.Extensions +namespace MQTTnet.Tests.Extensions; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttTopicTemplate_Tests { - [TestClass] - public sealed class MqttTopicTemplate_Tests + [TestMethod] + public void AcceptsEmptyValue() + { + var template = new MqttTopicTemplate("A/B/{foo}/D"); + template.WithParameter("foo", ""); + } + + [TestMethod] + public void AcceptsValidTopics() + { + _ = new MqttTopicTemplate("A"); + _ = new MqttTopicTemplate("/"); + _ = new MqttTopicTemplate("/A"); + _ = new MqttTopicTemplate("A/#"); + _ = new MqttTopicTemplate("$SYS/#"); + _ = new MqttTopicTemplate("////"); + _ = new MqttTopicTemplate("A/B/{foo}/D/"); + } + + [TestMethod] + public void BasicOps() + { + var template = new MqttTopicTemplate("A/B/{param}/D"); + Assert.AreEqual("A/B/C/D", template.WithParameter("param", "C").Template); + Assert.AreEqual("A/B/+/D", template.WithoutParameter("param").Template); + Assert.AreEqual("A/B/+/D", template.TopicFilter); + Assert.AreEqual("A/B/+/D/#", template.TopicTreeRootFilter); + + // corner cases + Assert.AreEqual("#", new MqttTopicTemplate("#").TopicFilter); + Assert.AreEqual("+", new MqttTopicTemplate("+").TopicFilter); + Assert.AreEqual("/", new MqttTopicTemplate("/").TopicFilter); + } + + [TestMethod] + public void BasicOpsWithHash() + { + var template = new MqttTopicTemplate("A/B/{param}/D/#"); + Assert.AreEqual("A/B/C/D/#", template.WithParameter("param", "C").Template); + Assert.AreEqual("A/B/+/D/#", template.WithoutParameter("param").Template); + Assert.AreEqual("A/B/+/D/#", template.TopicFilter); + Assert.AreEqual("A/B/+/D/#", template.TopicTreeRootFilter); + } + + [TestMethod] + public void CanonicalPrefixFilter() + { + var template1 = new MqttTopicTemplate("A/v1/{param}/F"); + var template2 = new MqttTopicTemplate("A/v1/E/F"); + var template3 = new MqttTopicTemplate("A/v1/E/F/G/H"); + var canonicalFilter = MqttTopicTemplate.FindCanonicalPrefix([template1, template2, template3]); + // possible improvement: Assert.AreEqual("A/v1/+/F", canonicalFilter.TopicFilter); + Assert.AreEqual("A/v1/+", canonicalFilter.TopicFilter); + Assert.AreEqual("A/v1/+/#", canonicalFilter.TopicTreeRootFilter); + + var template2B = new MqttTopicTemplate("A/v1/E/X"); + canonicalFilter = MqttTopicTemplate.FindCanonicalPrefix([template1, template2, template3, template2B]); + Assert.AreEqual("A/v1/+", canonicalFilter.Template); + Assert.AreEqual("A/v1/+", canonicalFilter.TopicFilter); + Assert.AreEqual("A/v1/+/#", canonicalFilter.TopicTreeRootFilter); + + + var template4 = new MqttTopicTemplate("A/v2/{prefix}"); + var canonicalFilter2 = MqttTopicTemplate.FindCanonicalPrefix([template1, template2, template3, template4]); + Assert.AreEqual("A/+", canonicalFilter2.TopicFilter); + Assert.AreEqual("A/+/#", canonicalFilter2.TopicTreeRootFilter); + } + + [TestMethod] + public void CanonicalPrefixFilterSimple1() + { + var template = MqttTopicTemplate.FindCanonicalPrefix( + [ + new MqttTopicTemplate("A"), + new MqttTopicTemplate("B") + ]); + + Assert.AreEqual("#", template.TopicFilter); + Assert.AreEqual("#", template.Template); + } + + [TestMethod] + public void CanonicalPrefixFilterSimple2() { - [TestMethod] - public void BasicOps() - { - var template = new MqttTopicTemplate("A/B/{param}/D"); - Assert.AreEqual("A/B/C/D", template.WithParameter("param", "C").Template); - Assert.AreEqual("A/B/+/D", template.WithoutParameter("param").Template); - Assert.AreEqual("A/B/+/D", template.TopicFilter); - Assert.AreEqual("A/B/+/D/#", template.TopicTreeRootFilter); - - // corner cases - Assert.AreEqual("#", new MqttTopicTemplate("#").TopicFilter); - Assert.AreEqual("+", new MqttTopicTemplate("+").TopicFilter); - Assert.AreEqual("/", new MqttTopicTemplate("/").TopicFilter); - } - - [TestMethod] - public void BasicOpsWithHash() - { - var template = new MqttTopicTemplate("A/B/{param}/D/#"); - Assert.AreEqual("A/B/C/D/#", template.WithParameter("param", "C").Template); - Assert.AreEqual("A/B/+/D/#", template.WithoutParameter("param").Template); - Assert.AreEqual("A/B/+/D/#", template.TopicFilter); - Assert.AreEqual("A/B/+/D/#", template.TopicTreeRootFilter); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void RejectsReservedChars1() - { - var template = new MqttTopicTemplate("A/B/{foo}/D"); - template.WithParameter("foo", "a#"); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void RejectsReservedChars2() - { - var template = new MqttTopicTemplate("A/B/{foo}/D"); - template.WithParameter("foo", "a+b"); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void RejectsReservedChars3() - { - var template = new MqttTopicTemplate("A/B/{foo}/D"); - template.WithParameter("foo", "a/b"); - } - - [TestMethod] - public void AcceptsEmptyValue() - { - var template = new MqttTopicTemplate("A/B/{foo}/D"); - template.WithParameter("foo", ""); - } - - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void RejectsEmptyTemplate() - { - var _ = new MqttTopicTemplate(""); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentNullException))] - public void RejectsNullTemplate() - { - var _ = new MqttTopicTemplate(null); - } - - [TestMethod] - public void IgnoresEmptyParameters() - { - var template = new MqttTopicTemplate("A/B/{}/D"); - Assert.IsFalse(template.Parameters.Any()); - } - - [TestMethod] - public void AcceptsValidTopics() - { - _ = new MqttTopicTemplate("A"); - _ = new MqttTopicTemplate("/"); - _ = new MqttTopicTemplate("/A"); - _ = new MqttTopicTemplate("A/#"); - _ = new MqttTopicTemplate("$SYS/#"); - _ = new MqttTopicTemplate("////"); - _ = new MqttTopicTemplate("A/B/{foo}/D/"); - } - - [TestMethod] - public void DynamicRoutingSupport() - { - var v1template = new MqttTopicTemplate("A/v1/{id}/F"); - var v2template = new MqttTopicTemplate("A/v2/foo/{id}/{bar}"); - var incoming_v1_topic = "A/v1/32/F"; - - var compat_v2_topic = v2template - .WithParameterValuesFrom( - v1template.ParseParameterValues(incoming_v1_topic)) - .WithParameter("bar", "unknown"); - - // id: 32 was relocated and bar: unknown added - Assert.AreEqual("A/v2/foo/32/unknown", compat_v2_topic.Template); - } - - [TestMethod] - public void SubscriptionSupport() - { - var template = new MqttTopicTemplate("A/v1/{param}/F"); - var filter = template.BuildFilter() - .WithAtLeastOnceQoS() - .WithNoLocal() - .Build(); - - Assert.AreEqual("A/v1/+/F", filter.Topic); - } - - [TestMethod] - public void SubscriptionSupport2() - { - var template = new MqttTopicTemplate("A/v1/{param}/F"); - - var subscribeOptions = new MqttClientFactory().CreateSubscribeOptionsBuilder() - .WithTopicTemplate(template) - .WithSubscriptionIdentifier(5) - .Build(); - - Assert.AreEqual("A/v1/+/F", subscribeOptions.TopicFilters[0].Topic); - } - - [TestMethod] - public void SendAndSubscribeSupport() - { - var template = new MqttTopicTemplate("App/v1/{sender}/message"); - - var filter = new MqttTopicFilterBuilder() - .WithTopicTemplate(template) - .WithAtLeastOnceQoS() - .WithNoLocal() - .Build(); - - Assert.AreEqual("App/v1/+/message", filter.Topic); - - var myTopic = template.WithParameter("sender", "me, myself & i"); - - var message = new MqttApplicationMessageBuilder() - .WithTopicTemplate( - myTopic) - .WithPayload("Hello!") - .Build(); - - Assert.IsTrue(message.MatchesTopicTemplate(template)); - } - - [TestMethod] - public void SendAndSubscribeSupport2() - { - var template = new MqttTopicTemplate("App/v1/{sender}/message"); - Assert.ThrowsException(() => - template.BuildMessage()); - } - - [TestMethod] - public void CanonicalPrefixFilter() - { - var template1 = new MqttTopicTemplate("A/v1/{param}/F"); - var template2 = new MqttTopicTemplate("A/v1/E/F"); - var template3 = new MqttTopicTemplate("A/v1/E/F/G/H"); - var canonicalFilter = MqttTopicTemplate.FindCanonicalPrefix(new[] { template1, template2, template3 }); - // possible improvement: Assert.AreEqual("A/v1/+/F", canonicalFilter.TopicFilter); - Assert.AreEqual("A/v1/+", canonicalFilter.TopicFilter); - Assert.AreEqual("A/v1/+/#", canonicalFilter.TopicTreeRootFilter); - - var template2b = new MqttTopicTemplate("A/v1/E/X"); - canonicalFilter = MqttTopicTemplate.FindCanonicalPrefix(new[] { template1, template2, template3, template2b }); - Assert.AreEqual("A/v1/+", canonicalFilter.Template); - Assert.AreEqual("A/v1/+", canonicalFilter.TopicFilter); - Assert.AreEqual("A/v1/+/#", canonicalFilter.TopicTreeRootFilter); - - - var template4 = new MqttTopicTemplate("A/v2/{prefix}"); - var canonicalFilter2 = MqttTopicTemplate.FindCanonicalPrefix(new[] { template1, template2, template3, template4 }); - Assert.AreEqual("A/+", canonicalFilter2.TopicFilter); - Assert.AreEqual("A/+/#", canonicalFilter2.TopicTreeRootFilter); - } - - [TestMethod] - public void CanonicalPrefixFilterSimple1() - { - var template = MqttTopicTemplate.FindCanonicalPrefix(new[] - { - new MqttTopicTemplate("A"), - new MqttTopicTemplate("B") - }); - Assert.AreEqual("#", template.TopicFilter); - Assert.AreEqual("#", template.Template); - } - - [TestMethod] - public void CanonicalPrefixFilterSimple2() - { - var template = MqttTopicTemplate.FindCanonicalPrefix(new[] - { - new MqttTopicTemplate("A/v1/{param}/F/"), - new MqttTopicTemplate("A/v1/E/X") - }); - Assert.AreEqual("A/v1/+", template.TopicFilter); - } - - [TestMethod] - public void CanonicalPrefixFilterSimple3() - { - var template = MqttTopicTemplate.FindCanonicalPrefix(new[] - { - new MqttTopicTemplate("A/v1/{param}/F"), - new MqttTopicTemplate("A/v1/{param}/F/X") - }); - Assert.AreEqual("A/v1/+/+", template.TopicFilter); - Assert.AreEqual("A/v1/{param}/+", template.Template); - } - - [TestMethod] - public void CanonicalPrefixFilterSimple4() - { - var template = MqttTopicTemplate.FindCanonicalPrefix(new[] - { - new MqttTopicTemplate("A/v1/{param}/FFA"), - new MqttTopicTemplate("A/v1/{param}/FX") - }); - Assert.AreEqual("A/v1/+/+", template.TopicFilter); - Assert.AreEqual("A/v1/{param}/+", template.Template); - } - - [TestMethod] - public void CanonicalPrefixFilterSimple5() - { - var template = MqttTopicTemplate.FindCanonicalPrefix(new[] - { - new MqttTopicTemplate("A/v1/E/F/G/H"), - new MqttTopicTemplate("A/v2/{param}") - }); - Assert.AreEqual("A/+", template.TopicFilter); - Assert.AreEqual("A/+/#", template.TopicTreeRootFilter); - } - - [TestMethod] - public void CanonicalPrefixFilterSimple6() - { - var template = MqttTopicTemplate.FindCanonicalPrefix(new[] - { - new MqttTopicTemplate("A/{version}/E/F/G/H"), - new MqttTopicTemplate("A/{version}/{param}") - }); - Assert.AreEqual("A/+/+", template.TopicFilter); - Assert.AreEqual("A/+/+/#", template.TopicTreeRootFilter); - } + var template = MqttTopicTemplate.FindCanonicalPrefix( + [ + new MqttTopicTemplate("A/v1/{param}/F/"), + new MqttTopicTemplate("A/v1/E/X") + ]); + + Assert.AreEqual("A/v1/+", template.TopicFilter); + } + + [TestMethod] + public void CanonicalPrefixFilterSimple3() + { + var template = MqttTopicTemplate.FindCanonicalPrefix( + [ + new MqttTopicTemplate("A/v1/{param}/F"), + new MqttTopicTemplate("A/v1/{param}/F/X") + ]); + + Assert.AreEqual("A/v1/+/+", template.TopicFilter); + Assert.AreEqual("A/v1/{param}/+", template.Template); + } + + [TestMethod] + public void CanonicalPrefixFilterSimple4() + { + var template = MqttTopicTemplate.FindCanonicalPrefix( + [ + new MqttTopicTemplate("A/v1/{param}/FFA"), + new MqttTopicTemplate("A/v1/{param}/FX") + ]); + + Assert.AreEqual("A/v1/+/+", template.TopicFilter); + Assert.AreEqual("A/v1/{param}/+", template.Template); + } + + [TestMethod] + public void CanonicalPrefixFilterSimple5() + { + var template = MqttTopicTemplate.FindCanonicalPrefix( + [ + new MqttTopicTemplate("A/v1/E/F/G/H"), + new MqttTopicTemplate("A/v2/{param}") + ]); + + Assert.AreEqual("A/+", template.TopicFilter); + Assert.AreEqual("A/+/#", template.TopicTreeRootFilter); + } + + [TestMethod] + public void CanonicalPrefixFilterSimple6() + { + var template = MqttTopicTemplate.FindCanonicalPrefix( + [ + new MqttTopicTemplate("A/{version}/E/F/G/H"), + new MqttTopicTemplate("A/{version}/{param}") + ]); + + Assert.AreEqual("A/+/+", template.TopicFilter); + Assert.AreEqual("A/+/+/#", template.TopicTreeRootFilter); + } + + [TestMethod] + public void DynamicRoutingSupport() + { + var v1Template = new MqttTopicTemplate("A/v1/{id}/F"); + var v2Template = new MqttTopicTemplate("A/v2/foo/{id}/{bar}"); + var incomingV1Topic = "A/v1/32/F"; + + var compatV2Topic = v2Template.WithParameterValuesFrom(v1Template.ParseParameterValues(incomingV1Topic)).WithParameter("bar", "unknown"); + + // id: 32 was relocated and bar: unknown added + Assert.AreEqual("A/v2/foo/32/unknown", compatV2Topic.Template); + } + + [TestMethod] + public void IgnoresEmptyParameters() + { + var template = new MqttTopicTemplate("A/B/{}/D"); + Assert.IsFalse(template.Parameters.Any()); + } + + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void RejectsEmptyTemplate() + { + _ = new MqttTopicTemplate(""); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void RejectsNullTemplate() + { + _ = new MqttTopicTemplate(null); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void RejectsReservedChars1() + { + var template = new MqttTopicTemplate("A/B/{foo}/D"); + template.WithParameter("foo", "a#"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void RejectsReservedChars2() + { + var template = new MqttTopicTemplate("A/B/{foo}/D"); + template.WithParameter("foo", "a+b"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void RejectsReservedChars3() + { + var template = new MqttTopicTemplate("A/B/{foo}/D"); + template.WithParameter("foo", "a/b"); + } + + [TestMethod] + public void SendAndSubscribeSupport() + { + var template = new MqttTopicTemplate("App/v1/{sender}/message"); + + var filter = new MqttTopicFilterBuilder().WithTopicTemplate(template).WithAtLeastOnceQoS().WithNoLocal().Build(); + + Assert.AreEqual("App/v1/+/message", filter.Topic); + + var myTopic = template.WithParameter("sender", "me, myself & i"); + + var message = new MqttApplicationMessageBuilder().WithTopicTemplate(myTopic).WithPayload("Hello!").Build(); + + Assert.IsTrue(message.MatchesTopicTemplate(template)); + } + + [TestMethod] + public void SendAndSubscribeSupport2() + { + var template = new MqttTopicTemplate("App/v1/{sender}/message"); + Assert.ThrowsException(() => template.BuildMessage()); + } + + [TestMethod] + public void SubscriptionSupport() + { + var template = new MqttTopicTemplate("A/v1/{param}/F"); + var filter = template.BuildFilter().WithAtLeastOnceQoS().WithNoLocal().Build(); + + Assert.AreEqual("A/v1/+/F", filter.Topic); + } + + [TestMethod] + public void SubscriptionSupport2() + { + var template = new MqttTopicTemplate("A/v1/{param}/F"); + + var subscribeOptions = new MqttClientFactory().CreateSubscribeOptionsBuilder().WithTopicTemplate(template).WithSubscriptionIdentifier(5).Build(); + + Assert.AreEqual("A/v1/+/F", subscribeOptions.TopicFilters[0].Topic); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs b/Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs index 02dddbfa8..622f2fb77 100644 --- a/Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs +++ b/Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs @@ -14,268 +14,242 @@ using MQTTnet.Protocol; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Extensions +namespace MQTTnet.Tests.Extensions; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Rpc_Tests : BaseTestClass { - [TestClass] - public sealed class Rpc_Tests : BaseTestClass + [TestMethod] + public async Task Execute_Success_MQTT_V5_Mixed_Clients() { - [TestMethod] - public async Task Execute_Success_MQTT_V5_Mixed_Clients() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - var responseSender = await testEnvironment.ConnectClient(); - await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping"); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + var responseSender = await testEnvironment.ConnectClient(); + await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping"); - responseSender.ApplicationMessageReceivedAsync += async e => - { - Assert.IsNull(e.ApplicationMessage.ResponseTopic); - await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong"); - }; + responseSender.ApplicationMessageReceivedAsync += async e => + { + Assert.IsNull(e.ApplicationMessage.ResponseTopic); + await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong").ConfigureAwait(false); + }; - var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) - { - var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); + using var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); + var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); - Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); - } - } - } + Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); + } - [TestMethod] - public async Task Execute_Success_Parameters_Propagated_Correctly() + [TestMethod] + public async Task Execute_Success_Parameters_Propagated_Correctly() + { + var paramValue = "123"; + var parameters = new Dictionary { - var paramValue = "123"; - var parameters = new Dictionary - { - { TestParametersTopicGenerationStrategy.ExpectedParamName, "123" } - }; + { TestParametersTopicGenerationStrategy.ExpectedParamName, "123" } + }; - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); - await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}"); + var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); + await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}"); - responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong"); + responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong"); - using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder() - .WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build())) - { - var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters); + using var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder() + .WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()); + var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters); - Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); - } - } - } + Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); + } - [TestMethod] - public Task Execute_Success_With_QoS_0() - { - return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311); - } + [TestMethod] + public Task Execute_Success_With_QoS_0() + { + return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311); + } - [TestMethod] - public Task Execute_Success_With_QoS_0_MQTT_V5() - { - return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500); - } + [TestMethod] + public Task Execute_Success_With_QoS_0_MQTT_V5() + { + return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500); + } - [TestMethod] - public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic() - { - return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce); - } + [TestMethod] + public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic() + { + return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce); + } - [TestMethod] - public Task Execute_Success_With_QoS_1() - { - return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311); - } + [TestMethod] + public Task Execute_Success_With_QoS_1() + { + return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311); + } - [TestMethod] - public Task Execute_Success_With_QoS_1_MQTT_V5() - { - return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500); - } + [TestMethod] + public Task Execute_Success_With_QoS_1_MQTT_V5() + { + return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500); + } - [TestMethod] - public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic() - { - return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce); - } + [TestMethod] + public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic() + { + return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce); + } - [TestMethod] - public Task Execute_Success_With_QoS_2() - { - return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311); - } + [TestMethod] + public Task Execute_Success_With_QoS_2() + { + return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311); + } - [TestMethod] - public Task Execute_Success_With_QoS_2_MQTT_V5() - { - return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500); - } + [TestMethod] + public Task Execute_Success_With_QoS_2_MQTT_V5() + { + return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500); + } - [TestMethod] - public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic() - { - return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.ExactlyOnce); - } + [TestMethod] + public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic() + { + return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.ExactlyOnce); + } - [TestMethod] - [ExpectedException(typeof(MqttCommunicationTimedOutException))] - public async Task Execute_Timeout() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + [ExpectedException(typeof(MqttCommunicationTimedOutException))] + public async Task Execute_Timeout() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var requestSender = await testEnvironment.ConnectClient(); + var requestSender = await testEnvironment.ConnectClient(); - var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); - await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); - } - } + var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); + await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); + } - [TestMethod] - [ExpectedException(typeof(MqttCommunicationTimedOutException))] - public async Task Execute_Timeout_MQTT_V5_Mixed_Clients() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); - var responseSender = await testEnvironment.ConnectClient(); - await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping"); - - responseSender.ApplicationMessageReceivedAsync += async e => - { - Assert.IsNull(e.ApplicationMessage.ResponseTopic); - await CompletedTask.Instance; - }; - - var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - - using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) - { - await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); - } - } - } + [TestMethod] + [ExpectedException(typeof(MqttCommunicationTimedOutException))] + public async Task Execute_Timeout_MQTT_V5_Mixed_Clients() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); + var responseSender = await testEnvironment.ConnectClient(); + await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping"); - [TestMethod] - [ExpectedException(typeof(MqttCommunicationTimedOutException))] - public async Task Execute_With_Custom_Topic_Names() + responseSender.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + Assert.IsNull(e.ApplicationMessage.ResponseTopic); + return CompletedTask.Instance; + }; - var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build()); + var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); - } - } + using var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); + await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); + } - [TestMethod] - public void Use_Factory() - { - var factory = new MqttClientFactory(); - using (var client = factory.CreateMqttClient()) - { - var rpcClient = factory.CreateMqttRpcClient(client); + [TestMethod] + [ExpectedException(typeof(MqttCommunicationTimedOutException))] + public async Task Execute_With_Custom_Topic_Names() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - Assert.IsNotNull(rpcClient); - } - } + var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build()); - async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion) - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(protocolVersion)); - await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", qosLevel); + await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); + } + + [TestMethod] + public void Use_Factory() + { + var factory = new MqttClientFactory(); + using var client = factory.CreateMqttClient(); + var rpcClient = factory.CreateMqttRpcClient(client); + + Assert.IsNotNull(rpcClient); + } - responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong"); + async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion) + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(protocolVersion)); + await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", qosLevel); - var requestSender = await testEnvironment.ConnectClient(); + responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong"); - using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) - { - var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel); + var requestSender = await testEnvironment.ConnectClient(); - Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); - } - } - } + using var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); + var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel); - async Task Execute_Success_MQTT_V5(MqttQualityOfServiceLevel qosLevel) - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", qosLevel); + Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); + } - responseSender.ApplicationMessageReceivedAsync += async e => - { - await responseSender.PublishStringAsync(e.ApplicationMessage.ResponseTopic, "pong"); - }; + async Task Execute_Success_MQTT_V5(MqttQualityOfServiceLevel qosLevel) + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", qosLevel); - var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + responseSender.ApplicationMessageReceivedAsync += async e => + { + await responseSender.PublishStringAsync(e.ApplicationMessage.ResponseTopic, "pong").ConfigureAwait(false); + }; - using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) - { - var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel); + var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); - } - } - } + using var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); + var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", qosLevel); + + Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); + } - class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy + class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy + { + public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) { - public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) + return new MqttRpcTopicPair { - return new MqttRpcTopicPair - { - RequestTopic = "a", - ResponseTopic = "b" - }; - } + RequestTopic = "a", + ResponseTopic = "b" + }; } + } + + class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy + { + internal const string ExpectedParamName = "test_param_name"; - class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy + public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) { - internal const string ExpectedParamName = "test_param_name"; + if (context.Parameters == null) + { + throw new InvalidOperationException("Parameters dictionary expected to be not null"); + } - public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) + if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue)) { - if (context.Parameters == null) - { - throw new InvalidOperationException("Parameters dictionary expected to be not null"); - } - - if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue)) - { - throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present"); - } - - var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}"; - var responseTopic = requestTopic + "/response"; - - return new MqttRpcTopicPair - { - RequestTopic = requestTopic, - ResponseTopic = responseTopic - }; + throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present"); } + + var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}"; + var responseTopic = requestTopic + "/response"; + + return new MqttRpcTopicPair + { + RequestTopic = requestTopic, + ResponseTopic = responseTopic + }; } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Factory/MqttFactory_Tests.cs b/Source/MQTTnet.Tests/Factory/MqttFactory_Tests.cs index 5220cee36..e262a1214 100644 --- a/Source/MQTTnet.Tests/Factory/MqttFactory_Tests.cs +++ b/Source/MQTTnet.Tests/Factory/MqttFactory_Tests.cs @@ -5,91 +5,90 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Server; -namespace MQTTnet.Tests.Factory +namespace MQTTnet.Tests.Factory; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttFactory_Tests : BaseTestClass { - [TestClass] - public sealed class MqttFactory_Tests : BaseTestClass + [TestMethod] + public void Create_ApplicationMessageBuilder() + { + var factory = new MqttClientFactory(); + var builder = factory.CreateApplicationMessageBuilder(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void Create_ClientOptionsBuilder() + { + var factory = new MqttClientFactory(); + var builder = factory.CreateClientOptionsBuilder(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void Create_ServerOptionsBuilder() + { + var factory = new MqttServerFactory(); + var builder = factory.CreateServerOptionsBuilder(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void Create_SubscribeOptionsBuilder() + { + var factory = new MqttClientFactory(); + var builder = factory.CreateSubscribeOptionsBuilder(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void Create_UnsubscribeOptionsBuilder() + { + var factory = new MqttClientFactory(); + var builder = factory.CreateUnsubscribeOptionsBuilder(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void Create_TopicFilterBuilder() + { + var factory = new MqttClientFactory(); + var builder = factory.CreateTopicFilterBuilder(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void Create_MqttServer() + { + var factory = new MqttServerFactory(); + var server = factory.CreateMqttServer(new MqttServerOptionsBuilder().Build()); + + Assert.IsNotNull(server); + } + + [TestMethod] + public void Create_MqttClient() + { + var factory = new MqttClientFactory(); + var client = factory.CreateMqttClient(); + + Assert.IsNotNull(client); + } + + [TestMethod] + public void Create_LowLevelMqttClient() { + var factory = new MqttClientFactory(); + var client = factory.CreateLowLevelMqttClient(); - [TestMethod] - public void Create_ApplicationMessageBuilder() - { - var factory = new MqttClientFactory(); - var builder = factory.CreateApplicationMessageBuilder(); - - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Create_ClientOptionsBuilder() - { - var factory = new MqttClientFactory(); - var builder = factory.CreateClientOptionsBuilder(); - - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Create_ServerOptionsBuilder() - { - var factory = new MqttServerFactory(); - var builder = factory.CreateServerOptionsBuilder(); - - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Create_SubscribeOptionsBuilder() - { - var factory = new MqttClientFactory(); - var builder = factory.CreateSubscribeOptionsBuilder(); - - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Create_UnsubscribeOptionsBuilder() - { - var factory = new MqttClientFactory(); - var builder = factory.CreateUnsubscribeOptionsBuilder(); - - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Create_TopicFilterBuilder() - { - var factory = new MqttClientFactory(); - var builder = factory.CreateTopicFilterBuilder(); - - Assert.IsNotNull(builder); - } - - [TestMethod] - public void Create_MqttServer() - { - var factory = new MqttServerFactory(); - var server = factory.CreateMqttServer(new MqttServerOptionsBuilder().Build()); - - Assert.IsNotNull(server); - } - - [TestMethod] - public void Create_MqttClient() - { - var factory = new MqttClientFactory(); - var client = factory.CreateMqttClient(); - - Assert.IsNotNull(client); - } - - [TestMethod] - public void Create_LowLevelMqttClient() - { - var factory = new MqttClientFactory(); - var client = factory.CreateLowLevelMqttClient(); - - Assert.IsNotNull(client); - } + Assert.IsNotNull(client); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs index 84fb48e04..0779b091a 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs @@ -9,303 +9,303 @@ using MQTTnet.Exceptions; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Formatter +namespace MQTTnet.Tests.Formatter; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttBufferReader_Tests { - [TestClass] - public sealed class MqttBufferReader_Tests + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")] + public void Fire_Exception_If_Not_Enough_Data() { - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")] - public void Fire_Exception_If_Not_Enough_Data() - { - var buffer = new byte[] { 0, 1, 2 }; + var buffer = new byte[] { 0, 1, 2 }; - var reader = new MqttBufferReader(); + var reader = new MqttBufferReader(); - reader.SetBuffer(buffer, 0, 3); + reader.SetBuffer(buffer, 0, 3); - // 1 byte is missing. - reader.ReadFourByteInteger(); - } + // 1 byte is missing. + reader.ReadFourByteInteger(); + } - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")] - public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer() - { - var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")] + public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer() + { + var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 }; - var reader = new MqttBufferReader(); + var reader = new MqttBufferReader(); - reader.SetBuffer(buffer, 0, 3); + reader.SetBuffer(buffer, 0, 3); - // 1 byte is missing. - reader.ReadFourByteInteger(); - } + // 1 byte is missing. + reader.ReadFourByteInteger(); + } - [TestMethod] - public void Is_EndOfStream_Without_Buffer() - { - var reader = new MqttBufferReader(); + [TestMethod] + public void Is_EndOfStream_Without_Buffer() + { + var reader = new MqttBufferReader(); - Assert.IsTrue(reader.EndOfStream); - Assert.AreEqual(0, reader.BytesLeft); - } + Assert.IsTrue(reader.EndOfStream); + Assert.AreEqual(0, reader.BytesLeft); + } - [TestMethod] - public void Read_Remaining_Data_From_Larger_Buffer() - { - var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + [TestMethod] + public void Read_Remaining_Data_From_Larger_Buffer() + { + var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - var reader = new MqttBufferReader(); + var reader = new MqttBufferReader(); - // The used buffer contains more data than used! - reader.SetBuffer(buffer, 0, 5); + // The used buffer contains more data than used! + reader.SetBuffer(buffer, 0, 5); - // This should only read 5 bytes even if more data is in the buffer - // due to custom bounds. - var remainingData = reader.ReadRemainingData(); + // This should only read 5 bytes even if more data is in the buffer + // due to custom bounds. + var remainingData = reader.ReadRemainingData(); - Assert.IsTrue(reader.EndOfStream); - Assert.AreEqual(0, reader.BytesLeft); - Assert.AreEqual(5, remainingData.Length); - } + Assert.IsTrue(reader.EndOfStream); + Assert.AreEqual(0, reader.BytesLeft); + Assert.AreEqual(5, remainingData.Length); + } - [TestMethod] - public void Read_Various_Positions_and_Offsets() - { - const int NumBufferElements = 1000; - const int NumTestSequences = 10000; + [TestMethod] + public void Read_Various_Positions_and_Offsets() + { + const int numBufferElements = 1000; + const int numTestSequences = 10000; - const string TestString = "The quick brown fox jumps over the lazy dog."; + const string testString = "The quick brown fox jumps over the lazy dog."; - var rndSeed = Environment.TickCount; - // rndSeed = ...; // assign random seed here if there is an error so that it can be repeated - var rnd = new Random(rndSeed); - // rndSeed is printed when an assert fails + var rndSeed = Environment.TickCount; + // rndSeed = ...; // assign random seed here if there is an error so that it can be repeated + var rnd = new Random(rndSeed); + // rndSeed is printed when an assert fails - var bufferElements = new List(); + var bufferElements = new List(); - byte[] elementBuffer; + byte[] elementBuffer; - // Fill a buffer with strings or numbers in big endian format - using (var ms = new MemoryStream()) + // Fill a buffer with strings or numbers in big endian format + using (var ms = new MemoryStream()) + { + using (var bw = new BinaryWriter(ms)) { - using (var bw = new BinaryWriter(ms)) - { - var bufferOffset = 0; + var bufferOffset = 0; - for (var i = 0; i < NumBufferElements; i++) - { - var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes); - - byte[] elementBytes = null; - uint elementNumberValue = 0; // all element number values fit into uint - string elementStringValue = null; - var elementSize = 0; - - var alreadyBigEndian = false; - switch (nextElementType) - { - case ElementReference.BufferElementType.Byte: - var byteValue = (byte)i; - elementNumberValue = byteValue; - elementSize = sizeof(byte); - elementBytes = new byte[1] { byteValue }; - alreadyBigEndian = true; // nothing to swap - break; - case ElementReference.BufferElementType.TwoByteInt: - var ushortValue = (ushort)i; - elementNumberValue = ushortValue; - elementSize = sizeof(ushort); - elementBytes = BitConverter.GetBytes(ushortValue); - break; - case ElementReference.BufferElementType.FourByteInt: - var uintValue = (uint)i; - elementNumberValue = uintValue; - elementSize = sizeof(uint); - elementBytes = BitConverter.GetBytes(uintValue); - break; - case ElementReference.BufferElementType.VariableSizeInt: - { - elementNumberValue = (uint)i; - var writer = new MqttBufferWriter(4, 4); - writer.WriteVariableByteInteger(elementNumberValue); - elementSize = writer.Length; - elementBytes = new byte[elementSize]; - var buffer = writer.GetBuffer(); - Array.Copy(buffer, elementBytes, elementSize); - alreadyBigEndian = true; // nothing to swap - } - break; - case ElementReference.BufferElementType.String: - { - var stringLen = rnd.Next(TestString.Length); - elementStringValue = TestString.Substring(0, stringLen); // could be empty - var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1); - writer.WriteString(elementStringValue); - elementSize = writer.Length; - elementBytes = new byte[elementSize]; - var buffer = writer.GetBuffer(); - Array.Copy(buffer, elementBytes, elementSize); - alreadyBigEndian = true; // nothing to swap - } - break; - } - - if (BitConverter.IsLittleEndian && !alreadyBigEndian) - { - Array.Reverse(elementBytes); - } - - bw.Write(elementBytes); - bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset)); - bufferOffset += elementSize; - } - - elementBuffer = ms.ToArray(); - } - } - - for (var i = 0; i < NumTestSequences; i++) - { - var firstElementIndex = rnd.Next(NumBufferElements - 1); - // segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count - var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1; - var lastElementIndex = firstElementIndex + elementCount - 1; - // Use element index to get buffer offsets - var lastElement = bufferElements[lastElementIndex]; - var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset; - var segmentEndPosition = lastElement.BufferOffset + lastElement.Size; - var segmentLength = segmentEndPosition - segmentStartPosition; - - var reader = new MqttBufferReader(); - reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength); - - // read all elements in the buffer segment; values should be as expected - for (var n = 0; n < elementCount; n++) + for (var i = 0; i < numBufferElements; i++) { - var element = bufferElements[firstElementIndex + n]; - uint elementNumberValue = 0; - string elementStringValue = null; + var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes); - var expectedPosition = element.BufferOffset - segmentStartPosition; - Assert.AreEqual(expectedPosition, reader.Position); + byte[] elementBytes = null; + uint elementNumberValue = 0; // all element number values fit into uint + string elementStringValue = null; + var elementSize = 0; - switch (element.Type) + var alreadyBigEndian = false; + switch (nextElementType) { case ElementReference.BufferElementType.Byte: - { - elementNumberValue = reader.ReadByte(); - } + var byteValue = (byte)i; + elementNumberValue = byteValue; + elementSize = sizeof(byte); + elementBytes = [byteValue]; + alreadyBigEndian = true; // nothing to swap break; case ElementReference.BufferElementType.TwoByteInt: - { - elementNumberValue = reader.ReadTwoByteInteger(); - } + var ushortValue = (ushort)i; + elementNumberValue = ushortValue; + elementSize = sizeof(ushort); + elementBytes = BitConverter.GetBytes(ushortValue); break; case ElementReference.BufferElementType.FourByteInt: - { - elementNumberValue = reader.ReadFourByteInteger(); - } + var uintValue = (uint)i; + elementNumberValue = uintValue; + elementSize = sizeof(uint); + elementBytes = BitConverter.GetBytes(uintValue); break; case ElementReference.BufferElementType.VariableSizeInt: { - elementNumberValue = reader.ReadVariableByteInteger(); + elementNumberValue = (uint)i; + var writer = new MqttBufferWriter(4, 4); + writer.WriteVariableByteInteger(elementNumberValue); + elementSize = writer.Length; + elementBytes = new byte[elementSize]; + var buffer = writer.GetBuffer(); + Array.Copy(buffer, elementBytes, elementSize); + alreadyBigEndian = true; // nothing to swap } break; case ElementReference.BufferElementType.String: { - elementStringValue = reader.ReadString(); + var stringLen = rnd.Next(testString.Length); + elementStringValue = testString[..stringLen]; // could be empty + var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1); + writer.WriteString(elementStringValue); + elementSize = writer.Length; + elementBytes = new byte[elementSize]; + var buffer = writer.GetBuffer(); + Array.Copy(buffer, elementBytes, elementSize); + alreadyBigEndian = true; // nothing to swap } break; } - if (elementStringValue != null) - { - Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}."); - } - else + if (BitConverter.IsLittleEndian && !alreadyBigEndian) { - Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}."); + Array.Reverse(elementBytes); } + + bw.Write(elementBytes); + bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset)); + bufferOffset += elementSize; } - // confirm end of stream - Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed); - Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed); + elementBuffer = ms.ToArray(); } } - [TestMethod] - public void Report_Correct_Length_For_Full_Buffer() + for (var i = 0; i < numTestSequences; i++) { - var buffer = new byte[] { 5, 6, 7, 8, 9 }; + var firstElementIndex = rnd.Next(numBufferElements - 1); + // segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count + var elementCount = rnd.Next(numBufferElements - firstElementIndex) + 1; + var lastElementIndex = firstElementIndex + elementCount - 1; + // Use element index to get buffer offsets + var lastElement = bufferElements[lastElementIndex]; + var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset; + var segmentEndPosition = lastElement.BufferOffset + lastElement.Size; + var segmentLength = segmentEndPosition - segmentStartPosition; var reader = new MqttBufferReader(); - reader.SetBuffer(buffer, 0, 5); + reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength); - Assert.IsFalse(reader.EndOfStream); - Assert.AreEqual(5, reader.BytesLeft); - } + // read all elements in the buffer segment; values should be as expected + for (var n = 0; n < elementCount; n++) + { + var element = bufferElements[firstElementIndex + n]; + uint elementNumberValue = 0; + string elementStringValue = null; - [TestMethod] - public void Report_Correct_Length_For_Partial_End_Buffer() - { - var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + var expectedPosition = element.BufferOffset - segmentStartPosition; + Assert.AreEqual(expectedPosition, reader.Position); - var reader = new MqttBufferReader(); + switch (element.Type) + { + case ElementReference.BufferElementType.Byte: + { + elementNumberValue = reader.ReadByte(); + } + break; + case ElementReference.BufferElementType.TwoByteInt: + { + elementNumberValue = reader.ReadTwoByteInteger(); + } + break; + case ElementReference.BufferElementType.FourByteInt: + { + elementNumberValue = reader.ReadFourByteInteger(); + } + break; + case ElementReference.BufferElementType.VariableSizeInt: + { + elementNumberValue = reader.ReadVariableByteInteger(); + } + break; + case ElementReference.BufferElementType.String: + { + elementStringValue = reader.ReadString(); + } + break; + } - // The used buffer contains more data than used! - reader.SetBuffer(buffer, 5, 5); + if (elementStringValue != null) + { + Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}."); + } + else + { + Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}."); + } + } - Assert.IsFalse(reader.EndOfStream); - Assert.AreEqual(5, reader.BytesLeft); + // confirm end of stream + Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed); + Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed); } + } - [TestMethod] - public void Report_Correct_Length_For_Partial_Start_Buffer() - { - var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + [TestMethod] + public void Report_Correct_Length_For_Full_Buffer() + { + var buffer = new byte[] { 5, 6, 7, 8, 9 }; - var reader = new MqttBufferReader(); + var reader = new MqttBufferReader(); + reader.SetBuffer(buffer, 0, 5); - // The used buffer contains more data than used! - reader.SetBuffer(buffer, 0, 5); + Assert.IsFalse(reader.EndOfStream); + Assert.AreEqual(5, reader.BytesLeft); + } - Assert.IsFalse(reader.EndOfStream); - Assert.AreEqual(5, reader.BytesLeft); - } + [TestMethod] + public void Report_Correct_Length_For_Partial_End_Buffer() + { + var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + var reader = new MqttBufferReader(); - // Helper class to build up a reference to elements of various types in a buffer - class ElementReference - { - public enum BufferElementType - { - Byte, - TwoByteInt, - FourByteInt, - VariableSizeInt, - String - } + // The used buffer contains more data than used! + reader.SetBuffer(buffer, 5, 5); - public const int NumBufferElementTypes = 5; + Assert.IsFalse(reader.EndOfStream); + Assert.AreEqual(5, reader.BytesLeft); + } - public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset) - { - Type = type; - Size = size; - NumberValue = numberValue; - StringValue = stringValue; - BufferOffset = bufferOffset; - } + [TestMethod] + public void Report_Correct_Length_For_Partial_Start_Buffer() + { + var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public int BufferOffset { get; } - public uint NumberValue { get; } - public int Size { get; } - public string StringValue { get; } - public BufferElementType Type { get; } + var reader = new MqttBufferReader(); + + // The used buffer contains more data than used! + reader.SetBuffer(buffer, 0, 5); + + Assert.IsFalse(reader.EndOfStream); + Assert.AreEqual(5, reader.BytesLeft); + } + + + // Helper class to build up a reference to elements of various types in a buffer + class ElementReference + { + public enum BufferElementType + { + Byte, + TwoByteInt, + FourByteInt, + VariableSizeInt, + String + } + + public const int NumBufferElementTypes = 5; + + public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset) + { + Type = type; + Size = size; + NumberValue = numberValue; + StringValue = stringValue; + BufferOffset = bufferOffset; } + + public int BufferOffset { get; } + public uint NumberValue { get; } + public int Size { get; } + public string StringValue { get; } + public BufferElementType Type { get; } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs index 8343e2f5a..5a8d4fd3f 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs @@ -4,9 +4,7 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.IO; -using System.Text; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; @@ -18,601 +16,593 @@ using MQTTnet.Tests.Helpers; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Formatter +namespace MQTTnet.Tests.Formatter; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttPacketSerialization_V3_Binary_Tests { - [TestClass] - public sealed class MqttPacketSerialization_V3_Binary_Tests + [TestMethod] + public void DeserializeV310_MqttConnAckPacket() { - [TestMethod] - public void DeserializeV310_MqttConnAckPacket() + var p = new MqttConnAckPacket { - var p = new MqttConnAckPacket - { - ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized - }; + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + }; - DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); - } + DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); + } - [TestMethod] - public void DeserializeV311_MqttConnAckPacket() + [TestMethod] + public void DeserializeV311_MqttConnAckPacket() + { + var p = new MqttConnAckPacket { - var p = new MqttConnAckPacket - { - IsSessionPresent = true, - ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized - }; + IsSessionPresent = true, + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + }; - DeserializeAndCompare(p, "IAIBBQ=="); - } + DeserializeAndCompare(p, "IAIBBQ=="); + } - [TestMethod] - public void DeserializeV311_MqttConnectPacket() + [TestMethod] + public void DeserializeV311_MqttConnectPacket() + { + var p = new MqttConnectPacket { - var p = new MqttConnectPacket - { - ClientId = "XYZ", - Password = Encoding.UTF8.GetBytes("PASS"), - Username = "USER", - KeepAlivePeriod = 123, - CleanSession = true - }; - - DeserializeAndCompare(p, "EBsABE1RVFQEwgB7AANYWVoABFVTRVIABFBBU1M="); - } + ClientId = "XYZ", + Password = "PASS"u8.ToArray(), + Username = "USER", + KeepAlivePeriod = 123, + CleanSession = true + }; - [TestMethod] - public void DeserializeV311_MqttConnectPacketWithWillMessage() - { - var p = new MqttConnectPacket - { - ClientId = "XYZ", - Password = Encoding.UTF8.GetBytes("PASS"), - Username = "USER", - KeepAlivePeriod = 123, - CleanSession = true, - WillFlag = true, - WillTopic = "My/last/will", - WillMessage = Encoding.UTF8.GetBytes("Good byte."), - WillQoS = MqttQualityOfServiceLevel.AtLeastOnce, - WillRetain = true - }; - - DeserializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw=="); - } + DeserializeAndCompare(p, "EBsABE1RVFQEwgB7AANYWVoABFVTRVIABFBBU1M="); + } + + [TestMethod] + public void DeserializeV311_MqttConnectPacketWithWillMessage() + { + var p = new MqttConnectPacket + { + ClientId = "XYZ", + Password = "PASS"u8.ToArray(), + Username = "USER", + KeepAlivePeriod = 123, + CleanSession = true, + WillFlag = true, + WillTopic = "My/last/will", + WillMessage = "Good byte."u8.ToArray(), + WillQoS = MqttQualityOfServiceLevel.AtLeastOnce, + WillRetain = true + }; + + DeserializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw=="); + } - [TestMethod] - public void DeserializeV311_MqttPubAckPacket() + [TestMethod] + public void DeserializeV311_MqttPubAckPacket() + { + var p = new MqttPubAckPacket { - var p = new MqttPubAckPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - DeserializeAndCompare(p, "QAIAew=="); - } + DeserializeAndCompare(p, "QAIAew=="); + } - [TestMethod] - public void DeserializeV311_MqttPubCompPacket() + [TestMethod] + public void DeserializeV311_MqttPubCompPacket() + { + var p = new MqttPubCompPacket { - var p = new MqttPubCompPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - DeserializeAndCompare(p, "cAIAew=="); - } + DeserializeAndCompare(p, "cAIAew=="); + } - [TestMethod] - public void DeserializeV311_MqttPublishPacket() + [TestMethod] + public void DeserializeV311_MqttPublishPacket() + { + var p = new MqttPublishPacket { - var p = new MqttPublishPacket - { - PacketIdentifier = 123, - Dup = true, - Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("HELLO")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, - Topic = "A/B/C" - }; - - DeserializeAndCompare(p, "Ow4ABUEvQi9DAHtIRUxMTw=="); - } + PacketIdentifier = 123, + Dup = true, + Retain = true, + PayloadSegment = new ArraySegment("HELLO"u8.ToArray()), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + Topic = "A/B/C" + }; + DeserializeAndCompare(p, "Ow4ABUEvQi9DAHtIRUxMTw=="); + } - [TestMethod] - public void DeserializeV311_MqttPublishPacket_DupFalse() + [TestMethod] + public void DeserializeV311_MqttPublishPacket_DupFalse() + { + var p = new MqttPublishPacket { - var p = new MqttPublishPacket - { - Dup = false - }; + Dup = false + }; - var p2 = Roundtrip(p); + var p2 = Roundtrip(p); - Assert.AreEqual(p.Dup, p2.Dup); - } + Assert.AreEqual(p.Dup, p2.Dup); + } - [TestMethod] - public void DeserializeV311_MqttPublishPacket_Qos1() + [TestMethod] + public void DeserializeV311_MqttPublishPacket_Qos1() + { + var p = new MqttPublishPacket { - var p = new MqttPublishPacket - { - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce - }; + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce + }; - var p2 = Roundtrip(p); + var p2 = Roundtrip(p); - Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); - Assert.AreEqual(p.Dup, p2.Dup); - } + Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); + Assert.AreEqual(p.Dup, p2.Dup); + } - [TestMethod] - public void DeserializeV311_MqttPublishPacket_Qos2() + [TestMethod] + public void DeserializeV311_MqttPublishPacket_Qos2() + { + var p = new MqttPublishPacket { - var p = new MqttPublishPacket - { - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, - PacketIdentifier = 1 - }; + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + PacketIdentifier = 1 + }; - var p2 = Roundtrip(p); + var p2 = Roundtrip(p); - Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); - Assert.AreEqual(p.Dup, p2.Dup); - } + Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); + Assert.AreEqual(p.Dup, p2.Dup); + } - [TestMethod] - public void DeserializeV311_MqttPublishPacket_Qos3() + [TestMethod] + public void DeserializeV311_MqttPublishPacket_Qos3() + { + var p = new MqttPublishPacket { - var p = new MqttPublishPacket - { - QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce, - PacketIdentifier = 1 - }; + QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce, + PacketIdentifier = 1 + }; - var p2 = Roundtrip(p); + var p2 = Roundtrip(p); - Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); - Assert.AreEqual(p.Dup, p2.Dup); - } + Assert.AreEqual(p.QualityOfServiceLevel, p2.QualityOfServiceLevel); + Assert.AreEqual(p.Dup, p2.Dup); + } - [TestMethod] - public void DeserializeV311_MqttPubRecPacket() + [TestMethod] + public void DeserializeV311_MqttPubRecPacket() + { + var p = new MqttPubRecPacket { - var p = new MqttPubRecPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - DeserializeAndCompare(p, "UAIAew=="); - } + DeserializeAndCompare(p, "UAIAew=="); + } - [TestMethod] - public void DeserializeV311_MqttPubRelPacket() + [TestMethod] + public void DeserializeV311_MqttPubRelPacket() + { + var p = new MqttPubRelPacket { - var p = new MqttPubRelPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - DeserializeAndCompare(p, "YgIAew=="); - } + DeserializeAndCompare(p, "YgIAew=="); + } - [TestMethod] - public void DeserializeV311_MqttSubAckPacket() - { - var p = new MqttSubAckPacket - { - PacketIdentifier = 123, - ReasonCodes = new List - { - MqttSubscribeReasonCode.GrantedQoS0, - MqttSubscribeReasonCode.GrantedQoS1, - MqttSubscribeReasonCode.GrantedQoS2, - MqttSubscribeReasonCode.UnspecifiedError - } - }; - - DeserializeAndCompare(p, "kAYAewABAoA="); - } + [TestMethod] + public void DeserializeV311_MqttSubAckPacket() + { + var p = new MqttSubAckPacket + { + PacketIdentifier = 123, + ReasonCodes = + [ + MqttSubscribeReasonCode.GrantedQoS0, + MqttSubscribeReasonCode.GrantedQoS1, + MqttSubscribeReasonCode.GrantedQoS2, + MqttSubscribeReasonCode.UnspecifiedError + ] + }; + + DeserializeAndCompare(p, "kAYAewABAoA="); + } - [TestMethod] - public void DeserializeV311_MqttSubscribePacket() - { - var p = new MqttSubscribePacket - { - PacketIdentifier = 123, - TopicFilters = new List - { - new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }, - new MqttTopicFilter { Topic = "1/2/3", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }, - new MqttTopicFilter { Topic = "x/y/z", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce } - } - }; - - DeserializeAndCompare(p, "ghoAewAFQS9CL0MCAAUxLzIvMwEABXgveS96AA=="); - } + [TestMethod] + public void DeserializeV311_MqttSubscribePacket() + { + var p = new MqttSubscribePacket + { + PacketIdentifier = 123, + TopicFilters = + [ + new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }, + new MqttTopicFilter { Topic = "1/2/3", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }, + new MqttTopicFilter { Topic = "x/y/z", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce } + ] + }; + + DeserializeAndCompare(p, "ghoAewAFQS9CL0MCAAUxLzIvMwEABXgveS96AA=="); + } - [TestMethod] - public void DeserializeV311_MqttUnsubAckPacket() + [TestMethod] + public void DeserializeV311_MqttUnsubAckPacket() + { + var p = new MqttUnsubAckPacket { - var p = new MqttUnsubAckPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - DeserializeAndCompare(p, "sAIAew=="); - } + DeserializeAndCompare(p, "sAIAew=="); + } - [TestMethod] - public void DeserializeV311_MqttUnsubscribePacket() + [TestMethod] + public void DeserializeV311_MqttUnsubscribePacket() + { + var p = new MqttUnsubscribePacket { - var p = new MqttUnsubscribePacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - p.TopicFilters.Add("A/B/C"); - p.TopicFilters.Add("1/2/3"); - p.TopicFilters.Add("x/y/z"); + p.TopicFilters.Add("A/B/C"); + p.TopicFilters.Add("1/2/3"); + p.TopicFilters.Add("x/y/z"); - DeserializeAndCompare(p, "ohcAewAFQS9CL0MABTEvMi8zAAV4L3kveg=="); - } + DeserializeAndCompare(p, "ohcAewAFQS9CL0MABTEvMi8zAAV4L3kveg=="); + } - [TestMethod] - public void DetectVersionFromMqttConnectPacket() - { - var packet = new MqttConnectPacket - { - ClientId = "XYZ", - Password = Encoding.UTF8.GetBytes("PASS"), - Username = "USER", - KeepAlivePeriod = 123, - CleanSession = true - }; - - Assert.AreEqual( - MqttProtocolVersion.V310, - DeserializeAndDetectVersion(new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), Serialize(packet, MqttProtocolVersion.V310))); - - Assert.AreEqual( - MqttProtocolVersion.V311, - DeserializeAndDetectVersion(new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), Serialize(packet, MqttProtocolVersion.V311))); - - Assert.AreEqual( - MqttProtocolVersion.V500, - DeserializeAndDetectVersion(new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), Serialize(packet, MqttProtocolVersion.V500))); - - var adapter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); - - var ex = Assert.ThrowsException( - () => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[0]))); - Assert.AreEqual("CONNECT packet must have at least 7 bytes.", ex.Message); - ex = Assert.ThrowsException( - () => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[7]))); - Assert.AreEqual("Protocol '' not supported.", ex.Message); - ex = Assert.ThrowsException( - () => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[] { 255, 255, 0, 0, 0, 0, 0 }))); - Assert.AreEqual("Expected at least 65537 bytes but there are only 7 bytes", ex.Message); - } + [TestMethod] + public void DetectVersionFromMqttConnectPacket() + { + var packet = new MqttConnectPacket + { + ClientId = "XYZ", + Password = "PASS"u8.ToArray(), + Username = "USER", + KeepAlivePeriod = 123, + CleanSession = true + }; + + Assert.AreEqual( + MqttProtocolVersion.V310, + DeserializeAndDetectVersion(new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), Serialize(packet, MqttProtocolVersion.V310))); + + Assert.AreEqual( + MqttProtocolVersion.V311, + DeserializeAndDetectVersion(new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), Serialize(packet, MqttProtocolVersion.V311))); + + Assert.AreEqual( + MqttProtocolVersion.V500, + DeserializeAndDetectVersion(new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), Serialize(packet, MqttProtocolVersion.V500))); + + var adapter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); + + var ex = Assert.ThrowsException( + () => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, []))); + Assert.AreEqual("CONNECT packet must have at least 7 bytes.", ex.Message); + ex = Assert.ThrowsException( + () => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[7]))); + Assert.AreEqual("Protocol '' not supported.", ex.Message); + ex = Assert.ThrowsException( + () => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, [255, 255, 0, 0, 0, 0, 0]))); + Assert.AreEqual("Expected at least 65537 bytes but there are only 7 bytes", ex.Message); + } - [TestMethod] - public void Serialize_LargePacket() - { - const int payloadLength = 80000; + [TestMethod] + public void Serialize_LargePacket() + { + const int payloadLength = 80000; - var payload = new byte[payloadLength]; + var payload = new byte[payloadLength]; - var value = 0; - for (var i = 0; i < payloadLength; i++) + var value = 0; + for (var i = 0; i < payloadLength; i++) + { + if (value > 255) { - if (value > 255) - { - value = 0; - } - - payload[i] = (byte)value; + value = 0; } - var publishPacket = new MqttPublishPacket - { - Topic = "abcdefghijklmnopqrstuvwxyz0123456789", - PayloadSegment = new ArraySegment(payload) - }; + payload[i] = (byte)value; + value++; + } - var serializationHelper = new MqttPacketSerializationHelper(); + var publishPacket = new MqttPublishPacket + { + Topic = "abcdefghijklmnopqrstuvwxyz0123456789", + PayloadSegment = new ArraySegment(payload) + }; - var buffer = serializationHelper.Encode(publishPacket); - var publishPacketCopy = serializationHelper.Decode(buffer) as MqttPublishPacket; + var serializationHelper = new MqttPacketSerializationHelper(); - Assert.IsNotNull(publishPacketCopy); - Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy.Payload.ToArray()); + var buffer = serializationHelper.Encode(publishPacket); + var publishPacketCopy = serializationHelper.Decode(buffer) as MqttPublishPacket; - // Now modify the payload and test again. - publishPacket.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("MQTT")); + Assert.IsNotNull(publishPacketCopy); + Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy.Payload.ToArray()); - buffer = serializationHelper.Encode(publishPacket); - var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; + // Now modify the payload and test again. + publishPacket.PayloadSegment = new ArraySegment("MQTT"u8.ToArray()); - Assert.IsNotNull(publishPacketCopy2); - Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy2.Payload.ToArray()); - } + buffer = serializationHelper.Encode(publishPacket); + var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; + + Assert.IsNotNull(publishPacketCopy2); + Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy2.Payload.ToArray()); + } - [TestMethod] - public void SerializeV310_MqttConnAckPacket() + [TestMethod] + public void SerializeV310_MqttConnAckPacket() + { + var p = new MqttConnAckPacket { - var p = new MqttConnAckPacket - { - ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized - }; + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + }; - SerializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); - } + SerializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); + } - [TestMethod] - public void SerializeV310_MqttConnectPacket() + [TestMethod] + public void SerializeV310_MqttConnectPacket() + { + var p = new MqttConnectPacket { - var p = new MqttConnectPacket - { - ClientId = "XYZ", - Password = Encoding.UTF8.GetBytes("PASS"), - Username = "USER", - KeepAlivePeriod = 123, - CleanSession = true - }; - - SerializeAndCompare(p, "EB0ABk1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310); - } + ClientId = "XYZ", + Password = "PASS"u8.ToArray(), + Username = "USER", + KeepAlivePeriod = 123, + CleanSession = true + }; - [TestMethod] - public void SerializeV311_MqttConnAckPacket() + SerializeAndCompare(p, "EB0ABk1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310); + } + + [TestMethod] + public void SerializeV311_MqttConnAckPacket() + { + var p = new MqttConnAckPacket { - var p = new MqttConnAckPacket - { - IsSessionPresent = true, - ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized - }; + IsSessionPresent = true, + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized + }; - SerializeAndCompare(p, "IAIBBQ=="); - } + SerializeAndCompare(p, "IAIBBQ=="); + } - [TestMethod] - public void SerializeV311_MqttConnectPacket() + [TestMethod] + public void SerializeV311_MqttConnectPacket() + { + var p = new MqttConnectPacket { - var p = new MqttConnectPacket - { - ClientId = "XYZ", - Password = Encoding.UTF8.GetBytes("PASS"), - Username = "USER", - KeepAlivePeriod = 123, - CleanSession = true - }; - - SerializeAndCompare(p, "EBsABE1RVFQEwgB7AANYWVoABFVTRVIABFBBU1M="); - } + ClientId = "XYZ", + Password = "PASS"u8.ToArray(), + Username = "USER", + KeepAlivePeriod = 123, + CleanSession = true + }; - [TestMethod] - public void SerializeV311_MqttConnectPacketWithWillMessage() - { - var p = new MqttConnectPacket - { - ClientId = "XYZ", - Password = Encoding.UTF8.GetBytes("PASS"), - Username = "USER", - KeepAlivePeriod = 123, - CleanSession = true, - WillFlag = true, - WillTopic = "My/last/will", - WillMessage = Encoding.UTF8.GetBytes("Good byte."), - WillQoS = MqttQualityOfServiceLevel.AtLeastOnce, - WillRetain = true - }; - - SerializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw=="); - } + SerializeAndCompare(p, "EBsABE1RVFQEwgB7AANYWVoABFVTRVIABFBBU1M="); + } - [TestMethod] - public void SerializeV311_MqttDisconnectPacket() - { - SerializeAndCompare(new MqttDisconnectPacket(), "4AA="); - } + [TestMethod] + public void SerializeV311_MqttConnectPacketWithWillMessage() + { + var p = new MqttConnectPacket + { + ClientId = "XYZ", + Password = "PASS"u8.ToArray(), + Username = "USER", + KeepAlivePeriod = 123, + CleanSession = true, + WillFlag = true, + WillTopic = "My/last/will", + WillMessage = "Good byte."u8.ToArray(), + WillQoS = MqttQualityOfServiceLevel.AtLeastOnce, + WillRetain = true + }; + + SerializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw=="); + } - [TestMethod] - public void SerializeV311_MqttPingReqPacket() - { - SerializeAndCompare(new MqttPingReqPacket(), "wAA="); - } + [TestMethod] + public void SerializeV311_MqttDisconnectPacket() + { + SerializeAndCompare(new MqttDisconnectPacket(), "4AA="); + } - [TestMethod] - public void SerializeV311_MqttPingRespPacket() - { - SerializeAndCompare(new MqttPingRespPacket(), "0AA="); - } + [TestMethod] + public void SerializeV311_MqttPingReqPacket() + { + SerializeAndCompare(new MqttPingReqPacket(), "wAA="); + } - [TestMethod] - public void SerializeV311_MqttPubAckPacket() + [TestMethod] + public void SerializeV311_MqttPingRespPacket() + { + SerializeAndCompare(new MqttPingRespPacket(), "0AA="); + } + + [TestMethod] + public void SerializeV311_MqttPubAckPacket() + { + var p = new MqttPubAckPacket { - var p = new MqttPubAckPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - SerializeAndCompare(p, "QAIAew=="); - } + SerializeAndCompare(p, "QAIAew=="); + } - [TestMethod] - public void SerializeV311_MqttPubCompPacket() + [TestMethod] + public void SerializeV311_MqttPubCompPacket() + { + var p = new MqttPubCompPacket { - var p = new MqttPubCompPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - SerializeAndCompare(p, "cAIAew=="); - } + SerializeAndCompare(p, "cAIAew=="); + } - [TestMethod] - public void SerializeV311_MqttPublishPacket() + [TestMethod] + public void SerializeV311_MqttPublishPacket() + { + var p = new MqttPublishPacket { - var p = new MqttPublishPacket - { - PacketIdentifier = 123, - Dup = true, - Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("HELLO")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, - Topic = "A/B/C" - }; - - SerializeAndCompare(p, "Ow4ABUEvQi9DAHtIRUxMTw=="); - } + PacketIdentifier = 123, + Dup = true, + Retain = true, + PayloadSegment = new ArraySegment("HELLO"u8.ToArray()), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + Topic = "A/B/C" + }; + + SerializeAndCompare(p, "Ow4ABUEvQi9DAHtIRUxMTw=="); + } - [TestMethod] - public void SerializeV311_MqttPubRecPacket() + [TestMethod] + public void SerializeV311_MqttPubRecPacket() + { + var p = new MqttPubRecPacket { - var p = new MqttPubRecPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - SerializeAndCompare(p, "UAIAew=="); - } + SerializeAndCompare(p, "UAIAew=="); + } - [TestMethod] - public void SerializeV311_MqttPubRelPacket() + [TestMethod] + public void SerializeV311_MqttPubRelPacket() + { + var p = new MqttPubRelPacket { - var p = new MqttPubRelPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - SerializeAndCompare(p, "YgIAew=="); - } + SerializeAndCompare(p, "YgIAew=="); + } - [TestMethod] - public void SerializeV311_MqttSubAckPacket() - { - var p = new MqttSubAckPacket - { - PacketIdentifier = 123, - ReasonCodes = new List - { - MqttSubscribeReasonCode.GrantedQoS0, - MqttSubscribeReasonCode.GrantedQoS1, - MqttSubscribeReasonCode.GrantedQoS2, - MqttSubscribeReasonCode.UnspecifiedError - } - }; - - SerializeAndCompare(p, "kAYAewABAoA="); - } + [TestMethod] + public void SerializeV311_MqttSubAckPacket() + { + var p = new MqttSubAckPacket + { + PacketIdentifier = 123, + ReasonCodes = + [ + MqttSubscribeReasonCode.GrantedQoS0, + MqttSubscribeReasonCode.GrantedQoS1, + MqttSubscribeReasonCode.GrantedQoS2, + MqttSubscribeReasonCode.UnspecifiedError + ] + }; + + SerializeAndCompare(p, "kAYAewABAoA="); + } - [TestMethod] - public void SerializeV311_MqttSubscribePacket() + [TestMethod] + public void SerializeV311_MqttSubscribePacket() + { + var p = new MqttSubscribePacket { - var p = new MqttSubscribePacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - p.TopicFilters.Add(new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); - p.TopicFilters.Add(new MqttTopicFilter { Topic = "1/2/3", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - p.TopicFilters.Add(new MqttTopicFilter { Topic = "x/y/z", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + p.TopicFilters.Add(new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); + p.TopicFilters.Add(new MqttTopicFilter { Topic = "1/2/3", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + p.TopicFilters.Add(new MqttTopicFilter { Topic = "x/y/z", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - SerializeAndCompare(p, "ghoAewAFQS9CL0MCAAUxLzIvMwEABXgveS96AA=="); - } + SerializeAndCompare(p, "ghoAewAFQS9CL0MCAAUxLzIvMwEABXgveS96AA=="); + } - [TestMethod] - public void SerializeV311_MqttUnsubAckPacket() + [TestMethod] + public void SerializeV311_MqttUnsubAckPacket() + { + var p = new MqttUnsubAckPacket { - var p = new MqttUnsubAckPacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - SerializeAndCompare(p, "sAIAew=="); - } + SerializeAndCompare(p, "sAIAew=="); + } - [TestMethod] - public void SerializeV311_MqttUnsubscribePacket() + [TestMethod] + public void SerializeV311_MqttUnsubscribePacket() + { + var p = new MqttUnsubscribePacket { - var p = new MqttUnsubscribePacket - { - PacketIdentifier = 123 - }; + PacketIdentifier = 123 + }; - p.TopicFilters.Add("A/B/C"); - p.TopicFilters.Add("1/2/3"); - p.TopicFilters.Add("x/y/z"); + p.TopicFilters.Add("A/B/C"); + p.TopicFilters.Add("1/2/3"); + p.TopicFilters.Add("x/y/z"); - SerializeAndCompare(p, "ohcAewAFQS9CL0MABTEvMi8zAAV4L3kveg=="); - } + SerializeAndCompare(p, "ohcAewAFQS9CL0MABTEvMi8zAAV4L3kveg=="); + } - void DeserializeAndCompare(MqttPacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) - { - var writer = WriterFactory(); + void DeserializeAndCompare(MqttPacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) + { + var writer = WriterFactory(); - var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer); - var buffer1 = serializer.Encode(packet); + var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer); + var buffer1 = serializer.Encode(packet); - using (var headerStream = new MemoryStream(buffer1.Join().ToArray())) - { - using (var channel = new MemoryMqttChannel(headerStream)) - { - using (var adapter = new MqttChannelAdapter( - channel, - new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), - new MqttNetEventLogger())) - { - var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); - - var buffer2 = serializer.Encode(receivedPacket); - - Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer2.Join().ToArray())); - } - } - } - } + using var headerStream = new MemoryStream(buffer1.Join().ToArray()); + using var channel = new MemoryMqttChannel(headerStream); + using var adapter = new MqttChannelAdapter( + channel, + new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), + new MqttNetEventLogger()); + var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); - MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer) - { - var channel = new MemoryMqttChannel(buffer); - var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger()); + var buffer2 = serializer.Encode(receivedPacket); - adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); - return packetFormatterAdapter.ProtocolVersion; - } + Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer2.Join().ToArray())); + } - TPacket Roundtrip(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferReader bufferReader = null, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket - { - var writer = bufferWriter ?? WriterFactory(); - var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer); - var buffer = serializer.Encode(packet); + static MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer) + { + var channel = new MemoryMqttChannel(buffer); + var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger()); - using (var channel = new MemoryMqttChannel(buffer.Join().ToArray())) - { - var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); - return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); - } - } + adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); + return packetFormatterAdapter.ProtocolVersion; + } - byte[] Serialize(MqttPacket packet, MqttProtocolVersion protocolVersion) - { - return MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, WriterFactory()).Encode(packet).Join().ToArray(); - } + static TPacket Roundtrip(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket + { + var writer = bufferWriter ?? WriterFactory(); + var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer); + var buffer = serializer.Encode(packet); - void SerializeAndCompare(MqttPacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) - { - Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Serialize(packet, protocolVersion))); - } + using var channel = new MemoryMqttChannel(buffer.Join().ToArray()); + var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); + return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); + } - MqttBufferWriter WriterFactory() - { - return new MqttBufferWriter(4096, 65535); - } + static byte[] Serialize(MqttPacket packet, MqttProtocolVersion protocolVersion) + { + return MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, WriterFactory()).Encode(packet).Join().ToArray(); + } + + static void SerializeAndCompare(MqttPacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) + { + Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Serialize(packet, protocolVersion))); + } + + static MqttBufferWriter WriterFactory() + { + return new MqttBufferWriter(4096, 65535); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs index 92ee5aa6a..914445e9f 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs @@ -4,494 +4,437 @@ using System; using System.Buffers; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Formatter +namespace MQTTnet.Tests.Formatter; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttPacketSerialization_V3_Tests { - [TestClass] - public sealed class MqttPacketSerialization_V3_Tests + [TestMethod] + public void Serialize_Full_MqttAuthPacket_V311() { - [TestMethod] - public void Serialize_Full_MqttAuthPacket_V311() - { - var authPacket = new MqttAuthPacket(); - Assert.ThrowsException(() => MqttPacketSerializationHelper.EncodeAndDecodePacket(authPacket, MqttProtocolVersion.V311)); - } + var authPacket = new MqttAuthPacket(); + Assert.ThrowsException(() => MqttPacketSerializationHelper.EncodeAndDecodePacket(authPacket, MqttProtocolVersion.V311)); + } - [TestMethod] - public void Serialize_Full_MqttConnAckPacket_V311() - { - var connAckPacket = new MqttConnAckPacket - { - AuthenticationData = Encoding.UTF8.GetBytes("AuthenticationData"), - AuthenticationMethod = "AuthenticationMethod", - ReasonCode = MqttConnectReasonCode.ServerUnavailable, - ReasonString = "ReasonString", - ReceiveMaximum = 123, - ResponseInformation = "ResponseInformation", - RetainAvailable = true, - ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized, - ServerReference = "ServerReference", - AssignedClientIdentifier = "AssignedClientIdentifier", - IsSessionPresent = true, - MaximumPacketSize = 456, - MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce, - ServerKeepAlive = 789, - SessionExpiryInterval = 852, - SharedSubscriptionAvailable = true, - SubscriptionIdentifiersAvailable = true, - TopicAliasMaximum = 963, - WildcardSubscriptionAvailable = true, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(connAckPacket, MqttProtocolVersion.V311); - - CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 - //Assert.AreEqual(connAckPacket.ReasonCode, deserialized.ReasonCode); - Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.ReceiveMaximum); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.ResponseInformation); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.RetainAvailable); // Not supported in v3.1.1 - Assert.AreEqual(MqttConnectReturnCode.ConnectionRefusedNotAuthorized, deserialized.ReturnCode); - Assert.AreEqual(null, deserialized.ServerReference); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.AssignedClientIdentifier); // Not supported in v3.1.1 - Assert.AreEqual(connAckPacket.IsSessionPresent, deserialized.IsSessionPresent); - Assert.AreEqual(0U, deserialized.MaximumPacketSize); // Not supported in v3.1.1 - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, deserialized.MaximumQoS); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.ServerKeepAlive); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.SessionExpiryInterval); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.SharedSubscriptionAvailable); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.SubscriptionIdentifiersAvailable); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.TopicAliasMaximum); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.WildcardSubscriptionAvailable); - Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 - } - - [TestMethod] - public void Serialize_Full_MqttConnAckPacket_V310() - { - var connAckPacket = new MqttConnAckPacket - { - AuthenticationData = Encoding.UTF8.GetBytes("AuthenticationData"), - AuthenticationMethod = "AuthenticationMethod", - ReasonCode = MqttConnectReasonCode.ServerUnavailable, - ReasonString = "ReasonString", - ReceiveMaximum = 123, - ResponseInformation = "ResponseInformation", - RetainAvailable = true, - ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized, - ServerReference = "ServerReference", - AssignedClientIdentifier = "AssignedClientIdentifier", - IsSessionPresent = true, - MaximumPacketSize = 456, - MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce, - ServerKeepAlive = 789, - SessionExpiryInterval = 852, - SharedSubscriptionAvailable = true, - SubscriptionIdentifiersAvailable = true, - TopicAliasMaximum = 963, - WildcardSubscriptionAvailable = true, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(connAckPacket, MqttProtocolVersion.V310); - - CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 - //Assert.AreEqual(connAckPacket.ReasonCode, deserialized.ReasonCode); - Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.ReceiveMaximum); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.ResponseInformation); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.RetainAvailable); // Not supported in v3.1.1 - Assert.AreEqual(MqttConnectReturnCode.ConnectionRefusedNotAuthorized, deserialized.ReturnCode); - Assert.AreEqual(null, deserialized.ServerReference); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.AssignedClientIdentifier); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.IsSessionPresent); // Not supported in v3.1.0 <- ! - Assert.AreEqual(0U, deserialized.MaximumPacketSize); // Not supported in v3.1.1 - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, deserialized.MaximumQoS); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.ServerKeepAlive); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.SessionExpiryInterval); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.SharedSubscriptionAvailable); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.SubscriptionIdentifiersAvailable); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.TopicAliasMaximum); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.WildcardSubscriptionAvailable); - Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 - } - - [TestMethod] - public void Serialize_Full_MqttConnectPacket_V311() - { - var connectPacket = new MqttConnectPacket - { - Username = "Username", - Password = Encoding.UTF8.GetBytes("Password"), - ClientId = "ClientId", - AuthenticationData = Encoding.UTF8.GetBytes("AuthenticationData"), - AuthenticationMethod = "AuthenticationMethod", - CleanSession = true, - ReceiveMaximum = 123, - WillFlag = true, - WillTopic = "WillTopic", - WillMessage = Encoding.UTF8.GetBytes("WillMessage"), - WillRetain = true, - KeepAlivePeriod = 456, - MaximumPacketSize = 789, - RequestProblemInformation = true, - RequestResponseInformation = true, - SessionExpiryInterval = 27, - TopicAliasMaximum = 67, - WillContentType = "WillContentType", - WillCorrelationData = Encoding.UTF8.GetBytes("WillCorrelationData"), - WillDelayInterval = 782, - WillQoS = MqttQualityOfServiceLevel.ExactlyOnce, - WillResponseTopic = "WillResponseTopic", - WillMessageExpiryInterval = 542, - WillPayloadFormatIndicator = MqttPayloadFormatIndicator.CharacterData, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - }, - WillUserProperties = new List - { - new MqttUserProperty("WillFoo", "WillBar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(connectPacket, MqttProtocolVersion.V311); - - Assert.AreEqual(connectPacket.Username, deserialized.Username); - CollectionAssert.AreEqual(connectPacket.Password, deserialized.Password); - Assert.AreEqual(connectPacket.ClientId, deserialized.ClientId); - CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 - Assert.AreEqual(connectPacket.CleanSession, deserialized.CleanSession); - Assert.AreEqual(0L, deserialized.ReceiveMaximum); // Not supported in v3.1.1 - Assert.AreEqual(connectPacket.WillFlag, deserialized.WillFlag); - Assert.AreEqual(connectPacket.WillTopic, deserialized.WillTopic); - CollectionAssert.AreEqual(connectPacket.WillMessage, deserialized.WillMessage); - Assert.AreEqual(connectPacket.WillRetain, deserialized.WillRetain); - Assert.AreEqual(connectPacket.KeepAlivePeriod, deserialized.KeepAlivePeriod); - // MaximumPacketSize not available in MQTTv3. - // RequestProblemInformation not available in MQTTv3. - // RequestResponseInformation not available in MQTTv3. - // SessionExpiryInterval not available in MQTTv3. - // TopicAliasMaximum not available in MQTTv3. - // WillContentType not available in MQTTv3. - // WillCorrelationData not available in MQTTv3. - // WillDelayInterval not available in MQTTv3. - Assert.AreEqual(connectPacket.WillQoS, deserialized.WillQoS); - // WillResponseTopic not available in MQTTv3. - // WillMessageExpiryInterval not available in MQTTv3. - // WillPayloadFormatIndicator not available in MQTTv3. - Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 - Assert.IsNull(deserialized.WillUserProperties); // Not supported in v3.1.1 - } - - [TestMethod] - public void Serialize_Full_MqttDisconnectPacket_V311() + [TestMethod] + public void Serialize_Full_MqttConnAckPacket_V311() + { + var connAckPacket = new MqttConnAckPacket { - var disconnectPacket = new MqttDisconnectPacket - { - ReasonCode = MqttDisconnectReasonCode.NormalDisconnection, // MQTTv3 has no other values than this. - ReasonString = "ReasonString", - ServerReference = "ServerReference", - SessionExpiryInterval = 234, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(disconnectPacket, MqttProtocolVersion.V311); - - Assert.AreEqual(disconnectPacket.ReasonCode, deserialized.ReasonCode); - Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.ServerReference); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.SessionExpiryInterval); // Not supported in v3.1.1 - CollectionAssert.AreEqual(null, deserialized.UserProperties); - } + AuthenticationData = "AuthenticationData"u8.ToArray(), + AuthenticationMethod = "AuthenticationMethod", + ReasonCode = MqttConnectReasonCode.ServerUnavailable, + ReasonString = "ReasonString", + ReceiveMaximum = 123, + ResponseInformation = "ResponseInformation", + RetainAvailable = true, + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized, + ServerReference = "ServerReference", + AssignedClientIdentifier = "AssignedClientIdentifier", + IsSessionPresent = true, + MaximumPacketSize = 456, + MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce, + ServerKeepAlive = 789, + SessionExpiryInterval = 852, + SharedSubscriptionAvailable = true, + SubscriptionIdentifiersAvailable = true, + TopicAliasMaximum = 963, + WildcardSubscriptionAvailable = true, + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(connAckPacket, MqttProtocolVersion.V311); + + CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 + //Assert.AreEqual(connAckPacket.ReasonCode, deserialized.ReasonCode); + Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.ReceiveMaximum); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.ResponseInformation); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.RetainAvailable); // Not supported in v3.1.1 + Assert.AreEqual(MqttConnectReturnCode.ConnectionRefusedNotAuthorized, deserialized.ReturnCode); + Assert.AreEqual(null, deserialized.ServerReference); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.AssignedClientIdentifier); // Not supported in v3.1.1 + Assert.AreEqual(connAckPacket.IsSessionPresent, deserialized.IsSessionPresent); + Assert.AreEqual(0U, deserialized.MaximumPacketSize); // Not supported in v3.1.1 + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, deserialized.MaximumQoS); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.ServerKeepAlive); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.SessionExpiryInterval); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.SharedSubscriptionAvailable); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.SubscriptionIdentifiersAvailable); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.TopicAliasMaximum); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.WildcardSubscriptionAvailable); + Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 + } - [TestMethod] - public void Serialize_Full_MqttPingReqPacket_V311() + [TestMethod] + public void Serialize_Full_MqttConnAckPacket_V310() + { + var connAckPacket = new MqttConnAckPacket { - var pingReqPacket = new MqttPingReqPacket(); - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pingReqPacket, MqttProtocolVersion.V311); - - Assert.IsNotNull(deserialized); - } + AuthenticationData = "AuthenticationData"u8.ToArray(), + AuthenticationMethod = "AuthenticationMethod", + ReasonCode = MqttConnectReasonCode.ServerUnavailable, + ReasonString = "ReasonString", + ReceiveMaximum = 123, + ResponseInformation = "ResponseInformation", + RetainAvailable = true, + ReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized, + ServerReference = "ServerReference", + AssignedClientIdentifier = "AssignedClientIdentifier", + IsSessionPresent = true, + MaximumPacketSize = 456, + MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce, + ServerKeepAlive = 789, + SessionExpiryInterval = 852, + SharedSubscriptionAvailable = true, + SubscriptionIdentifiersAvailable = true, + TopicAliasMaximum = 963, + WildcardSubscriptionAvailable = true, + UserProperties = [new MqttUserProperty("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(connAckPacket, MqttProtocolVersion.V310); + + CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 + //Assert.AreEqual(connAckPacket.ReasonCode, deserialized.ReasonCode); + Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.ReceiveMaximum); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.ResponseInformation); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.RetainAvailable); // Not supported in v3.1.1 + Assert.AreEqual(MqttConnectReturnCode.ConnectionRefusedNotAuthorized, deserialized.ReturnCode); + Assert.AreEqual(null, deserialized.ServerReference); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.AssignedClientIdentifier); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.IsSessionPresent); // Not supported in v3.1.0 <- ! + Assert.AreEqual(0U, deserialized.MaximumPacketSize); // Not supported in v3.1.1 + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, deserialized.MaximumQoS); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.ServerKeepAlive); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.SessionExpiryInterval); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.SharedSubscriptionAvailable); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.SubscriptionIdentifiersAvailable); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.TopicAliasMaximum); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.WildcardSubscriptionAvailable); + Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 + } - [TestMethod] - public void Serialize_Full_MqttPingRespPacket_V311() + [TestMethod] + public void Serialize_Full_MqttConnectPacket_V311() + { + var connectPacket = new MqttConnectPacket { - var pingRespPacket = new MqttPingRespPacket(); - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pingRespPacket, MqttProtocolVersion.V311); - - Assert.IsNotNull(deserialized); - } + Username = "Username", + Password = "Password"u8.ToArray(), + ClientId = "ClientId", + AuthenticationData = "AuthenticationData"u8.ToArray(), + AuthenticationMethod = "AuthenticationMethod", + CleanSession = true, + ReceiveMaximum = 123, + WillFlag = true, + WillTopic = "WillTopic", + WillMessage = "WillMessage"u8.ToArray(), + WillRetain = true, + KeepAlivePeriod = 456, + MaximumPacketSize = 789, + RequestProblemInformation = true, + RequestResponseInformation = true, + SessionExpiryInterval = 27, + TopicAliasMaximum = 67, + WillContentType = "WillContentType", + WillCorrelationData = "WillCorrelationData"u8.ToArray(), + WillDelayInterval = 782, + WillQoS = MqttQualityOfServiceLevel.ExactlyOnce, + WillResponseTopic = "WillResponseTopic", + WillMessageExpiryInterval = 542, + WillPayloadFormatIndicator = MqttPayloadFormatIndicator.CharacterData, + UserProperties = [new("Foo", "Bar")], + WillUserProperties = [new("WillFoo", "WillBar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(connectPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(connectPacket.Username, deserialized.Username); + CollectionAssert.AreEqual(connectPacket.Password, deserialized.Password); + Assert.AreEqual(connectPacket.ClientId, deserialized.ClientId); + CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 + Assert.AreEqual(connectPacket.CleanSession, deserialized.CleanSession); + Assert.AreEqual(0L, deserialized.ReceiveMaximum); // Not supported in v3.1.1 + Assert.AreEqual(connectPacket.WillFlag, deserialized.WillFlag); + Assert.AreEqual(connectPacket.WillTopic, deserialized.WillTopic); + CollectionAssert.AreEqual(connectPacket.WillMessage, deserialized.WillMessage); + Assert.AreEqual(connectPacket.WillRetain, deserialized.WillRetain); + Assert.AreEqual(connectPacket.KeepAlivePeriod, deserialized.KeepAlivePeriod); + // MaximumPacketSize not available in MQTTv3. + // RequestProblemInformation not available in MQTTv3. + // RequestResponseInformation not available in MQTTv3. + // SessionExpiryInterval not available in MQTTv3. + // TopicAliasMaximum not available in MQTTv3. + // WillContentType not available in MQTTv3. + // WillCorrelationData not available in MQTTv3. + // WillDelayInterval not available in MQTTv3. + Assert.AreEqual(connectPacket.WillQoS, deserialized.WillQoS); + // WillResponseTopic not available in MQTTv3. + // WillMessageExpiryInterval not available in MQTTv3. + // WillPayloadFormatIndicator not available in MQTTv3. + Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 + Assert.IsNull(deserialized.WillUserProperties); // Not supported in v3.1.1 + } - [TestMethod] - public void Serialize_Full_MqttPubAckPacket_V311() + [TestMethod] + public void Serialize_Full_MqttDisconnectPacket_V311() + { + var disconnectPacket = new MqttDisconnectPacket { - var pubAckPacket = new MqttPubAckPacket - { - PacketIdentifier = 123, - ReasonCode = MqttPubAckReasonCode.NoMatchingSubscribers, - ReasonString = "ReasonString", - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubAckPacket, MqttProtocolVersion.V311); + ReasonCode = MqttDisconnectReasonCode.NormalDisconnection, // MQTTv3 has no other values than this. + ReasonString = "ReasonString", + ServerReference = "ServerReference", + SessionExpiryInterval = 234, + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(disconnectPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(disconnectPacket.ReasonCode, deserialized.ReasonCode); + Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.ServerReference); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.SessionExpiryInterval); // Not supported in v3.1.1 + CollectionAssert.AreEqual(null, deserialized.UserProperties); + } - Assert.AreEqual(pubAckPacket.PacketIdentifier, deserialized.PacketIdentifier); - Assert.AreEqual(MqttPubAckReasonCode.Success, deserialized.ReasonCode); // Not supported in v3.1.1 - Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 - CollectionAssert.AreEqual(null, deserialized.UserProperties); - } + [TestMethod] + public void Serialize_Full_MqttPingReqPacket_V311() + { + var pingReqPacket = new MqttPingReqPacket(); - [TestMethod] - public void Serialize_Full_MqttPubCompPacket_V311() - { - var pubCompPacket = new MqttPubCompPacket - { - PacketIdentifier = 123, - ReasonCode = MqttPubCompReasonCode.PacketIdentifierNotFound, - ReasonString = "ReasonString", - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pingReqPacket, MqttProtocolVersion.V311); - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubCompPacket, MqttProtocolVersion.V311); + Assert.IsNotNull(deserialized); + } - Assert.AreEqual(pubCompPacket.PacketIdentifier, deserialized.PacketIdentifier); - // ReasonCode not available in MQTTv3. - // ReasonString not available in MQTTv3. - // UserProperties not available in MQTTv3. - Assert.IsNull(deserialized.UserProperties); - } + [TestMethod] + public void Serialize_Full_MqttPingRespPacket_V311() + { + var pingRespPacket = new MqttPingRespPacket(); - [TestMethod] - public void Serialize_Full_MqttPublishPacket_V311() - { - var publishPacket = new MqttPublishPacket - { - PacketIdentifier = 123, - Dup = true, - Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("Payload")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, - Topic = "Topic", - ResponseTopic = "/Response", - ContentType = "Content-Type", - CorrelationData = Encoding.UTF8.GetBytes("CorrelationData"), - TopicAlias = 27, - SubscriptionIdentifiers = new List - { - 123 - }, - MessageExpiryInterval = 38, - PayloadFormatIndicator = MqttPayloadFormatIndicator.CharacterData, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(publishPacket, MqttProtocolVersion.V311); - - Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); - Assert.AreEqual(publishPacket.Dup, deserialized.Dup); - Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), deserialized.Payload.ToArray()); - Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); - Assert.AreEqual(publishPacket.Topic, deserialized.Topic); - Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. - Assert.AreEqual(null, deserialized.ContentType); // Not supported in v3.1.1. - CollectionAssert.AreEqual(null, deserialized.CorrelationData); // Not supported in v3.1.1. - Assert.AreEqual(0U, deserialized.TopicAlias); // Not supported in v3.1.1. - CollectionAssert.AreEqual(null, deserialized.SubscriptionIdentifiers); // Not supported in v3.1.1 - Assert.AreEqual(0U, deserialized.MessageExpiryInterval); // Not supported in v3.1.1 - Assert.AreEqual(MqttPayloadFormatIndicator.Unspecified, deserialized.PayloadFormatIndicator); // Not supported in v3.1.1 - Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 - } - - [TestMethod] - public void Serialize_Full_MqttPubRecPacket_V311() - { - var pubRecPacket = new MqttPubRecPacket - { - PacketIdentifier = 123, - ReasonCode = MqttPubRecReasonCode.UnspecifiedError, - ReasonString = "ReasonString", - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pingRespPacket, MqttProtocolVersion.V311); - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubRecPacket, MqttProtocolVersion.V311); + Assert.IsNotNull(deserialized); + } - Assert.AreEqual(pubRecPacket.PacketIdentifier, deserialized.PacketIdentifier); - // ReasonCode not available in MQTTv3. - // ReasonString not available in MQTTv3. - // UserProperties not available in MQTTv3. - Assert.IsNull(deserialized.UserProperties); - } + [TestMethod] + public void Serialize_Full_MqttPubAckPacket_V311() + { + var pubAckPacket = new MqttPubAckPacket + { + PacketIdentifier = 123, + ReasonCode = MqttPubAckReasonCode.NoMatchingSubscribers, + ReasonString = "ReasonString", + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubAckPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(pubAckPacket.PacketIdentifier, deserialized.PacketIdentifier); + Assert.AreEqual(MqttPubAckReasonCode.Success, deserialized.ReasonCode); // Not supported in v3.1.1 + Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 + CollectionAssert.AreEqual(null, deserialized.UserProperties); + } - [TestMethod] - public void Serialize_Full_MqttPubRelPacket_V311() + [TestMethod] + public void Serialize_Full_MqttPubCompPacket_V311() + { + var pubCompPacket = new MqttPubCompPacket { - var pubRelPacket = new MqttPubRelPacket - { - PacketIdentifier = 123, - ReasonCode = MqttPubRelReasonCode.PacketIdentifierNotFound, - ReasonString = "ReasonString", - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; + PacketIdentifier = 123, + ReasonCode = MqttPubCompReasonCode.PacketIdentifierNotFound, + ReasonString = "ReasonString", + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubCompPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(pubCompPacket.PacketIdentifier, deserialized.PacketIdentifier); + // ReasonCode not available in MQTTv3. + // ReasonString not available in MQTTv3. + // UserProperties not available in MQTTv3. + Assert.IsNull(deserialized.UserProperties); + } - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubRelPacket, MqttProtocolVersion.V311); + [TestMethod] + public void Serialize_Full_MqttPublishPacket_V311() + { + var publishPacket = new MqttPublishPacket + { + PacketIdentifier = 123, + Dup = true, + Retain = true, + PayloadSegment = new ArraySegment("Payload"u8.ToArray()), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + Topic = "Topic", + ResponseTopic = "/Response", + ContentType = "Content-Type", + CorrelationData = "CorrelationData"u8.ToArray(), + TopicAlias = 27, + SubscriptionIdentifiers = [123], + MessageExpiryInterval = 38, + PayloadFormatIndicator = MqttPayloadFormatIndicator.CharacterData, + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(publishPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); + Assert.AreEqual(publishPacket.Dup, deserialized.Dup); + Assert.AreEqual(publishPacket.Retain, deserialized.Retain); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), deserialized.Payload.ToArray()); + Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); + Assert.AreEqual(publishPacket.Topic, deserialized.Topic); + Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. + Assert.AreEqual(null, deserialized.ContentType); // Not supported in v3.1.1. + CollectionAssert.AreEqual(null, deserialized.CorrelationData); // Not supported in v3.1.1. + Assert.AreEqual(0U, deserialized.TopicAlias); // Not supported in v3.1.1. + CollectionAssert.AreEqual(null, deserialized.SubscriptionIdentifiers); // Not supported in v3.1.1 + Assert.AreEqual(0U, deserialized.MessageExpiryInterval); // Not supported in v3.1.1 + Assert.AreEqual(MqttPayloadFormatIndicator.Unspecified, deserialized.PayloadFormatIndicator); // Not supported in v3.1.1 + Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 + } - Assert.AreEqual(pubRelPacket.PacketIdentifier, deserialized.PacketIdentifier); - // ReasonCode not available in MQTTv3. - // ReasonString not available in MQTTv3. - // UserProperties not available in MQTTv3. - Assert.IsNull(deserialized.UserProperties); - } + [TestMethod] + public void Serialize_Full_MqttPubRecPacket_V311() + { + var pubRecPacket = new MqttPubRecPacket + { + PacketIdentifier = 123, + ReasonCode = MqttPubRecReasonCode.UnspecifiedError, + ReasonString = "ReasonString", + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubRecPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(pubRecPacket.PacketIdentifier, deserialized.PacketIdentifier); + // ReasonCode not available in MQTTv3. + // ReasonString not available in MQTTv3. + // UserProperties not available in MQTTv3. + Assert.IsNull(deserialized.UserProperties); + } - [TestMethod] - public void Serialize_Full_MqttSubAckPacket_V311() + [TestMethod] + public void Serialize_Full_MqttPubRelPacket_V311() + { + var pubRelPacket = new MqttPubRelPacket { - var subAckPacket = new MqttSubAckPacket - { - PacketIdentifier = 123, - ReasonString = "ReasonString", - ReasonCodes = new List - { - MqttSubscribeReasonCode.GrantedQoS1 - }, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(subAckPacket, MqttProtocolVersion.V311); - - Assert.AreEqual(subAckPacket.PacketIdentifier, deserialized.PacketIdentifier); - Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 - Assert.AreEqual(subAckPacket.ReasonCodes.Count, deserialized.ReasonCodes.Count); - Assert.AreEqual(subAckPacket.ReasonCodes[0], deserialized.ReasonCodes[0]); - CollectionAssert.AreEqual(null, deserialized.UserProperties); // Not supported in v3.1.1 - } - - [TestMethod] - public void Serialize_Full_MqttSubscribePacket_V311() + PacketIdentifier = 123, + ReasonCode = MqttPubRelReasonCode.PacketIdentifierNotFound, + ReasonString = "ReasonString", + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(pubRelPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(pubRelPacket.PacketIdentifier, deserialized.PacketIdentifier); + // ReasonCode not available in MQTTv3. + // ReasonString not available in MQTTv3. + // UserProperties not available in MQTTv3. + Assert.IsNull(deserialized.UserProperties); + } + + [TestMethod] + public void Serialize_Full_MqttSubAckPacket_V311() + { + var subAckPacket = new MqttSubAckPacket { - var subscribePacket = new MqttSubscribePacket - { - PacketIdentifier = 123, - SubscriptionIdentifier = 456, - TopicFilters = new List - { - new MqttTopicFilter - { - Topic = "Topic", - NoLocal = true, - RetainHandling = MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly, - RetainAsPublished = true, - QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce - } - }, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(subscribePacket, MqttProtocolVersion.V311); - - Assert.AreEqual(subscribePacket.PacketIdentifier, deserialized.PacketIdentifier); - Assert.AreEqual(0U, deserialized.SubscriptionIdentifier); // Not supported in v3.1.1 - Assert.AreEqual(1, deserialized.TopicFilters.Count); - Assert.AreEqual(subscribePacket.TopicFilters[0].Topic, deserialized.TopicFilters[0].Topic); - Assert.AreEqual(false, deserialized.TopicFilters[0].NoLocal); // Not supported in v3.1.1 - Assert.AreEqual(MqttRetainHandling.SendAtSubscribe, deserialized.TopicFilters[0].RetainHandling); // Not supported in v3.1.1 - Assert.AreEqual(false, deserialized.TopicFilters[0].RetainAsPublished); // Not supported in v3.1.1 - Assert.AreEqual(subscribePacket.TopicFilters[0].QualityOfServiceLevel, deserialized.TopicFilters[0].QualityOfServiceLevel); - CollectionAssert.AreEqual(null, deserialized.UserProperties); // Not supported in v3.1.1 - } - - [TestMethod] - public void Serialize_Full_MqttUnsubAckPacket_V311() + PacketIdentifier = 123, + ReasonString = "ReasonString", + ReasonCodes = [MqttSubscribeReasonCode.GrantedQoS1], + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(subAckPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(subAckPacket.PacketIdentifier, deserialized.PacketIdentifier); + Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 + Assert.AreEqual(subAckPacket.ReasonCodes.Count, deserialized.ReasonCodes.Count); + Assert.AreEqual(subAckPacket.ReasonCodes[0], deserialized.ReasonCodes[0]); + CollectionAssert.AreEqual(null, deserialized.UserProperties); // Not supported in v3.1.1 + } + + [TestMethod] + public void Serialize_Full_MqttSubscribePacket_V311() + { + var subscribePacket = new MqttSubscribePacket { - var unsubAckPacket = new MqttUnsubAckPacket - { - PacketIdentifier = 123, - ReasonCodes = new List + PacketIdentifier = 123, + SubscriptionIdentifier = 456, + TopicFilters = + [ + new() { - MqttUnsubscribeReasonCode.ImplementationSpecificError - }, - ReasonString = "ReasonString", - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") + Topic = "Topic", + NoLocal = true, + RetainHandling = MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly, + RetainAsPublished = true, + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce } - }; - - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(unsubAckPacket, MqttProtocolVersion.V311); + ], + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(subscribePacket, MqttProtocolVersion.V311); + + Assert.AreEqual(subscribePacket.PacketIdentifier, deserialized.PacketIdentifier); + Assert.AreEqual(0U, deserialized.SubscriptionIdentifier); // Not supported in v3.1.1 + Assert.AreEqual(1, deserialized.TopicFilters.Count); + Assert.AreEqual(subscribePacket.TopicFilters[0].Topic, deserialized.TopicFilters[0].Topic); + Assert.AreEqual(false, deserialized.TopicFilters[0].NoLocal); // Not supported in v3.1.1 + Assert.AreEqual(MqttRetainHandling.SendAtSubscribe, deserialized.TopicFilters[0].RetainHandling); // Not supported in v3.1.1 + Assert.AreEqual(false, deserialized.TopicFilters[0].RetainAsPublished); // Not supported in v3.1.1 + Assert.AreEqual(subscribePacket.TopicFilters[0].QualityOfServiceLevel, deserialized.TopicFilters[0].QualityOfServiceLevel); + CollectionAssert.AreEqual(null, deserialized.UserProperties); // Not supported in v3.1.1 + } - Assert.AreEqual(unsubAckPacket.PacketIdentifier, deserialized.PacketIdentifier); - Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 - CollectionAssert.AreEqual(null, deserialized.ReasonCodes); // Not supported in v3.1.1 - CollectionAssert.AreEqual(null, deserialized.UserProperties); // Not supported in v3.1.1 - } + [TestMethod] + public void Serialize_Full_MqttUnsubAckPacket_V311() + { + var unsubAckPacket = new MqttUnsubAckPacket + { + PacketIdentifier = 123, + ReasonCodes = [MqttUnsubscribeReasonCode.ImplementationSpecificError], + ReasonString = "ReasonString", + UserProperties = [new("Foo", "Bar")] + }; + + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(unsubAckPacket, MqttProtocolVersion.V311); + + Assert.AreEqual(unsubAckPacket.PacketIdentifier, deserialized.PacketIdentifier); + Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 + CollectionAssert.AreEqual(null, deserialized.ReasonCodes); // Not supported in v3.1.1 + CollectionAssert.AreEqual(null, deserialized.UserProperties); // Not supported in v3.1.1 + } - [TestMethod] - public void Serialize_Full_MqttUnsubscribePacket_V311() + [TestMethod] + public void Serialize_Full_MqttUnsubscribePacket_V311() + { + var unsubscribePacket = new MqttUnsubscribePacket { - var unsubscribePacket = new MqttUnsubscribePacket - { - PacketIdentifier = 123, - TopicFilters = new List - { - "TopicFilter1" - }, - UserProperties = new List - { - new MqttUserProperty("Foo", "Bar") - } - }; + PacketIdentifier = 123, + TopicFilters = ["TopicFilter1"], + UserProperties = [new("Foo", "Bar")] + }; - var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(unsubscribePacket, MqttProtocolVersion.V311); + var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(unsubscribePacket, MqttProtocolVersion.V311); - Assert.AreEqual(unsubscribePacket.PacketIdentifier, deserialized.PacketIdentifier); - Assert.AreEqual(unsubscribePacket.TopicFilters.Count, deserialized.TopicFilters.Count); - Assert.AreEqual(unsubscribePacket.TopicFilters[0], deserialized.TopicFilters[0]); - CollectionAssert.AreEqual(null, deserialized.UserProperties); - } + Assert.AreEqual(unsubscribePacket.PacketIdentifier, deserialized.PacketIdentifier); + Assert.AreEqual(unsubscribePacket.TopicFilters.Count, deserialized.TopicFilters.Count); + Assert.AreEqual(unsubscribePacket.TopicFilters[0], deserialized.TopicFilters[0]); + CollectionAssert.AreEqual(null, deserialized.UserProperties); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs index f74ba975a..2f327765c 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs @@ -11,6 +11,7 @@ namespace MQTTnet.Tests.Formatter; +// ReSharper disable InconsistentNaming [TestClass] public sealed class MqttPacketSerialization_V5_Tests { diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs index 7a16ff30a..35412e41a 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs @@ -1,102 +1,101 @@ using System; -using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Formatter +namespace MQTTnet.Tests.Formatter; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttPacketWriter_Tests { - [TestClass] - public sealed class MqttPacketWriter_Tests + [TestMethod] + public void Reset_After_Usage() { - [TestMethod] - public void Reset_After_Usage() - { - var writer = new MqttBufferWriter(4096, 65535); - writer.WriteString("AString"); - writer.WriteByte(0x1); - writer.WriteByte(0x0); - writer.WriteByte(0x1); - writer.WriteVariableByteInteger(1234U); - writer.WriteVariableByteInteger(9876U); + var writer = new MqttBufferWriter(4096, 65535); + writer.WriteString("AString"); + writer.WriteByte(0x1); + writer.WriteByte(0x0); + writer.WriteByte(0x1); + writer.WriteVariableByteInteger(1234U); + writer.WriteVariableByteInteger(9876U); - writer.Reset(0); + writer.Reset(0); - Assert.AreEqual(0, writer.Length); - } + Assert.AreEqual(0, writer.Length); + } - [TestMethod] - public void Use_All_Data_Types() - { - var writer = new MqttBufferWriter(4096, 65535); - writer.WriteString("AString"); - writer.WriteByte(0x1); - writer.WriteByte(0x0); - writer.WriteByte(0x1); - writer.WriteVariableByteInteger(1234U); - writer.WriteVariableByteInteger(9876U); + [TestMethod] + public void Use_All_Data_Types() + { + var writer = new MqttBufferWriter(4096, 65535); + writer.WriteString("AString"); + writer.WriteByte(0x1); + writer.WriteByte(0x0); + writer.WriteByte(0x1); + writer.WriteVariableByteInteger(1234U); + writer.WriteVariableByteInteger(9876U); - var buffer = writer.GetBuffer(); + var buffer = writer.GetBuffer(); - var reader = new MqttBufferReader(); - reader.SetBuffer(buffer, 0, writer.Length); + var reader = new MqttBufferReader(); + reader.SetBuffer(buffer, 0, writer.Length); - Assert.AreEqual("AString", reader.ReadString()); - Assert.IsTrue(reader.ReadByte() == 1); - Assert.IsTrue(reader.ReadByte() == 0); - Assert.IsTrue(reader.ReadByte() == 1); - Assert.AreEqual(1234U, reader.ReadVariableByteInteger()); - Assert.AreEqual(9876U, reader.ReadVariableByteInteger()); - } - - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void Throw_If_String_Too_Long() - { - var writer = new MqttBufferWriter(4096, 65535); - - writer.WriteString(string.Empty.PadLeft(65536)); - } + Assert.AreEqual("AString", reader.ReadString()); + Assert.IsTrue(reader.ReadByte() == 1); + Assert.IsTrue(reader.ReadByte() == 0); + Assert.IsTrue(reader.ReadByte() == 1); + Assert.AreEqual(1234U, reader.ReadVariableByteInteger()); + Assert.AreEqual(9876U, reader.ReadVariableByteInteger()); + } - [TestMethod] - public void Write_And_Read_Multiple_Times() - { - var writer = new MqttBufferWriter(4096, 65535); - writer.WriteString("A relative short string."); - writer.WriteBinary(new byte[1234]); - writer.WriteByte(0x01); - writer.WriteByte(0x02); - writer.WriteVariableByteInteger(5647382); - writer.WriteString("A relative short string."); - writer.WriteVariableByteInteger(8574489); - writer.WriteBinary(new byte[48]); - writer.WriteByte(2); - writer.WriteByte(0x02); - writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); - writer.WriteBinary(new byte[3]); + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void Throw_If_String_Too_Long() + { + var writer = new MqttBufferWriter(4096, 65535); + + writer.WriteString(string.Empty.PadLeft(65536)); + } + + [TestMethod] + public void Write_And_Read_Multiple_Times() + { + var writer = new MqttBufferWriter(4096, 65535); + writer.WriteString("A relative short string."); + writer.WriteBinary(new byte[1234]); + writer.WriteByte(0x01); + writer.WriteByte(0x02); + writer.WriteVariableByteInteger(5647382); + writer.WriteString("A relative short string."); + writer.WriteVariableByteInteger(8574489); + writer.WriteBinary(new byte[48]); + writer.WriteByte(2); + writer.WriteByte(0x02); + writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); + writer.WriteBinary(new byte[3]); - var readPayload = new ArraySegment(writer.GetBuffer(), 0, writer.Length).ToArray(); + var readPayload = new ArraySegment(writer.GetBuffer(), 0, writer.Length).ToArray(); - var reader = new MqttBufferReader(); - reader.SetBuffer(readPayload, 0, readPayload.Length); + var reader = new MqttBufferReader(); + reader.SetBuffer(readPayload, 0, readPayload.Length); - for (var i = 0; i < 100000; i++) - { - reader.Seek(0); + for (var i = 0; i < 100000; i++) + { + reader.Seek(0); - reader.ReadString(); - reader.ReadBinaryData(); - reader.ReadByte(); - reader.ReadByte(); - reader.ReadVariableByteInteger(); - reader.ReadString(); - reader.ReadVariableByteInteger(); - reader.ReadBinaryData(); - reader.ReadByte(); - reader.ReadByte(); - reader.ReadString(); - reader.ReadBinaryData(); - } + reader.ReadString(); + reader.ReadBinaryData(); + reader.ReadByte(); + reader.ReadByte(); + reader.ReadVariableByteInteger(); + reader.ReadString(); + reader.ReadVariableByteInteger(); + reader.ReadBinaryData(); + reader.ReadByte(); + reader.ReadByte(); + reader.ReadString(); + reader.ReadBinaryData(); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs b/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs index 836507293..20198de8f 100644 --- a/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs +++ b/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs @@ -5,15 +5,14 @@ using System; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Helpers +namespace MQTTnet.Tests.Helpers; + +public static class MqttClientExtensions { - public static class MqttClientExtensions + public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client) { - public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client) - { - ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(client); - return new TestApplicationMessageReceivedHandler(client); - } + return new TestApplicationMessageReceivedHandler(client); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs b/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs index 1c6e1bf11..def919ef1 100644 --- a/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs +++ b/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs @@ -5,16 +5,15 @@ using MQTTnet.Formatter; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Helpers +namespace MQTTnet.Tests.Helpers; + +public static class MqttPacketWriterExtensions { - public static class MqttPacketWriterExtensions + public static byte[] AddMqttHeader(this MqttBufferWriter writer, MqttControlPacketType header, byte[] body) { - public static byte[] AddMqttHeader(this MqttBufferWriter writer, MqttControlPacketType header, byte[] body) - { - writer.WriteByte(MqttBufferWriter.BuildFixedHeader(header)); - writer.WriteVariableByteInteger((uint)body.Length); - writer.WriteBinary(body, 0, body.Length); - return writer.GetBuffer(); - } + writer.WriteByte(MqttBufferWriter.BuildFixedHeader(header)); + writer.WriteVariableByteInteger((uint)body.Length); + writer.WriteBinary(body, 0, body.Length); + return writer.GetBuffer(); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs b/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs index 6e857aeb5..00c3d1533 100644 --- a/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs +++ b/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs @@ -5,21 +5,20 @@ using System; using System.Reflection; -namespace MQTTnet.Tests.Helpers +namespace MQTTnet.Tests.Helpers; + +public static class ReflectionExtensions { - public static class ReflectionExtensions + public static object GetFieldValue(this object source, string fieldName) { - public static object GetFieldValue(this object source, string fieldName) - { - ArgumentNullException.ThrowIfNull(source); - - var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); - if (field == null) - { - throw new ArgumentException($"Field {fieldName} not found."); - } + ArgumentNullException.ThrowIfNull(source); - return field.GetValue(source); + var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null) + { + throw new ArgumentException($"Field {fieldName} not found."); } + + return field.GetValue(source); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/AsyncEvent_Tests.cs b/Source/MQTTnet.Tests/Internal/AsyncEvent_Tests.cs index 94a960e58..f741675f8 100644 --- a/Source/MQTTnet.Tests/Internal/AsyncEvent_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/AsyncEvent_Tests.cs @@ -8,133 +8,133 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class AsyncEvent_Tests { - [TestClass] - public sealed class AsyncEvent_Tests + int _testEventAsyncCount; + + [TestMethod] + public async Task Attach_and_Detach_Sync_And_Async_EventHandler() { - int _testEventAsyncCount; + var testClass = new TestClass(); - [TestMethod] - public async Task Attach_and_Detach_Sync_And_Async_EventHandler() - { - var testClass = new TestClass(); + testClass.TestEventAsync += OnTestEventAsync; + testClass.TestEvent += OnTestEvent; - testClass.TestEventAsync += OnTestEventAsync; - testClass.TestEvent += OnTestEvent; + await testClass.FireEventAsync(EventArgs.Empty); + Assert.AreEqual(2, _testEventAsyncCount); - await testClass.FireEventAsync(EventArgs.Empty); - Assert.AreEqual(2, _testEventAsyncCount); + testClass.TestEvent -= OnTestEvent; - testClass.TestEvent -= OnTestEvent; + await testClass.FireEventAsync(EventArgs.Empty); + Assert.AreEqual(3, _testEventAsyncCount); - await testClass.FireEventAsync(EventArgs.Empty); - Assert.AreEqual(3, _testEventAsyncCount); + testClass.TestEventAsync -= OnTestEventAsync; - testClass.TestEventAsync -= OnTestEventAsync; + await testClass.FireEventAsync(EventArgs.Empty); + Assert.AreEqual(3, _testEventAsyncCount); + } - await testClass.FireEventAsync(EventArgs.Empty); - Assert.AreEqual(3, _testEventAsyncCount); - } + [TestMethod] + public async Task Attach_EventHandler() + { + var testClass = new TestClass(); - [TestMethod] - public async Task Attach_EventHandler() - { - var testClass = new TestClass(); + testClass.TestEventAsync += OnTestEventAsync; - testClass.TestEventAsync += OnTestEventAsync; + await testClass.FireEventAsync(EventArgs.Empty); - await testClass.FireEventAsync(EventArgs.Empty); + Assert.AreEqual(1, _testEventAsyncCount); + } - Assert.AreEqual(1, _testEventAsyncCount); - } + [TestMethod] + public async Task Attach_Sync_And_Async_EventHandler() + { + var testClass = new TestClass(); - [TestMethod] - public async Task Attach_Sync_And_Async_EventHandler() - { - var testClass = new TestClass(); + testClass.TestEventAsync += OnTestEventAsync; + testClass.TestEvent += OnTestEvent; - testClass.TestEventAsync += OnTestEventAsync; - testClass.TestEvent += OnTestEvent; + await testClass.FireEventAsync(EventArgs.Empty); - await testClass.FireEventAsync(EventArgs.Empty); + Assert.AreEqual(2, _testEventAsyncCount); + } - Assert.AreEqual(2, _testEventAsyncCount); - } + [TestMethod] + public async Task Detach_EventHandler() + { + var testClass = new TestClass(); - [TestMethod] - public async Task Detach_EventHandler() - { - var testClass = new TestClass(); + testClass.TestEventAsync += OnTestEventAsync; + testClass.TestEventAsync -= OnTestEventAsync; - testClass.TestEventAsync += OnTestEventAsync; - testClass.TestEventAsync -= OnTestEventAsync; + await testClass.FireEventAsync(EventArgs.Empty); - await testClass.FireEventAsync(EventArgs.Empty); + Assert.AreEqual(0, _testEventAsyncCount); + } - Assert.AreEqual(0, _testEventAsyncCount); - } + [TestMethod] + public void Has_Handlers() + { + var testClass = new TestClass(); + testClass.TestEventAsync += OnTestEventAsync; - [TestMethod] - public void Has_Handlers() - { - var testClass = new TestClass(); - testClass.TestEventAsync += OnTestEventAsync; + Assert.AreEqual(true, testClass.HasTestHandlers); + } - Assert.AreEqual(true, testClass.HasTestHandlers); - } + [TestMethod] + public void No_Handlers() + { + var testClass = new TestClass(); - [TestMethod] - public void No_Handlers() - { - var testClass = new TestClass(); + Assert.AreEqual(false, testClass.HasTestHandlers); + } - Assert.AreEqual(false, testClass.HasTestHandlers); - } + [TestMethod] + public void Remove_Handlers() + { + var testClass = new TestClass(); + testClass.TestEventAsync += OnTestEventAsync; + testClass.TestEventAsync -= OnTestEventAsync; - [TestMethod] - public void Remove_Handlers() - { - var testClass = new TestClass(); - testClass.TestEventAsync += OnTestEventAsync; - testClass.TestEventAsync -= OnTestEventAsync; + Assert.AreEqual(false, testClass.HasTestHandlers); + } - Assert.AreEqual(false, testClass.HasTestHandlers); - } + void OnTestEvent(EventArgs arg) + { + Interlocked.Increment(ref _testEventAsyncCount); + } - void OnTestEvent(EventArgs arg) + Task OnTestEventAsync(EventArgs arg) + { + Interlocked.Increment(ref _testEventAsyncCount); + return CompletedTask.Instance; + } + + sealed class TestClass + { + readonly AsyncEvent _testEvent = new(); + + public event Action TestEvent { - Interlocked.Increment(ref _testEventAsyncCount); + add => _testEvent.AddHandler(value); + remove => _testEvent.RemoveHandler(value); } - Task OnTestEventAsync(EventArgs arg) + public event Func TestEventAsync { - Interlocked.Increment(ref _testEventAsyncCount); - return CompletedTask.Instance; + add => _testEvent.AddHandler(value); + remove => _testEvent.RemoveHandler(value); } - sealed class TestClass + public bool HasTestHandlers => _testEvent.HasHandlers; + + public Task FireEventAsync(EventArgs eventArgs) { - readonly AsyncEvent _testEvent = new AsyncEvent(); - - public event Action TestEvent - { - add => _testEvent.AddHandler(value); - remove => _testEvent.RemoveHandler(value); - } - - public event Func TestEventAsync - { - add => _testEvent.AddHandler(value); - remove => _testEvent.RemoveHandler(value); - } - - public bool HasTestHandlers => _testEvent.HasHandlers; - - public Task FireEventAsync(EventArgs eventArgs) - { - return _testEvent.InvokeAsync(eventArgs); - } + return _testEvent.InvokeAsync(eventArgs); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/AsyncLock_Tests.cs b/Source/MQTTnet.Tests/Internal/AsyncLock_Tests.cs index e30fd63d9..30d4eb2f3 100644 --- a/Source/MQTTnet.Tests/Internal/AsyncLock_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/AsyncLock_Tests.cs @@ -9,283 +9,267 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class AsyncLock_Tests { - [TestClass] - public sealed class AsyncLock_Tests + [TestMethod] + public async Task Cancellation_Of_Awaiter() { - [TestMethod] - public async Task Cancellation_Of_Awaiter() - { - var @lock = new AsyncLock(); + var @lock = new AsyncLock(); - // This call will not yet "release" the lock due to missing _using_. - var releaser = await @lock.EnterAsync().ConfigureAwait(false); + // This call will not yet "release" the lock due to missing _using_. + var releaser = await @lock.EnterAsync().ConfigureAwait(false); - var counter = 0; + var counter = 0; - Debug.WriteLine("Prepared locked lock."); + Debug.WriteLine("Prepared locked lock."); - _ = Task.Run( - async () => + _ = Task.Run( + async () => + { + // SHOULD GET TIMEOUT! + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)); + using (await @lock.EnterAsync(timeout.Token)) { - // SHOULD GET TIMEOUT! - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1))) - { - using (await @lock.EnterAsync(timeout.Token)) - { - Debug.WriteLine("Task 1 incremented"); - counter++; - } - } - }); + Debug.WriteLine("Task 1 incremented"); + counter++; + } + }); - _ = Task.Run( - async () => + _ = Task.Run( + async () => + { + // SHOULD GET TIMEOUT! + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + using (await @lock.EnterAsync(timeout.Token)) { - // SHOULD GET TIMEOUT! - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2))) - { - using (await @lock.EnterAsync(timeout.Token)) - { - Debug.WriteLine("Task 2 incremented"); - counter++; - } - } - }); + Debug.WriteLine("Task 2 incremented"); + counter++; + } + }); - _ = Task.Run( - async () => + _ = Task.Run( + async () => + { + // SHOULD GET TIMEOUT! + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + using (await @lock.EnterAsync(timeout.Token)) { - // SHOULD GET TIMEOUT! - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - using (await @lock.EnterAsync(timeout.Token)) - { - Debug.WriteLine("Task 3 incremented"); - counter++; - } - } - }); + Debug.WriteLine("Task 3 incremented"); + counter++; + } + }); - _ = Task.Run( - async () => + _ = Task.Run( + async () => + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)); + using (await @lock.EnterAsync(timeout.Token)) { - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4))) - { - using (await @lock.EnterAsync(timeout.Token)) - { - Debug.WriteLine("Task 4 incremented"); - counter++; - } - } - }); + Debug.WriteLine("Task 4 incremented"); + counter++; + } + }); - _ = Task.Run( - async () => + _ = Task.Run( + async () => + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using (await @lock.EnterAsync(timeout.Token)) { - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) - { - using (await @lock.EnterAsync(timeout.Token)) - { - Debug.WriteLine("Task 5 incremented"); - counter++; - } - } - }); + Debug.WriteLine("Task 5 incremented"); + counter++; + } + }); + + _ = Task.Run( + async () => + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(6)); + using (await @lock.EnterAsync(timeout.Token)) + { + Debug.WriteLine("Task 6 incremented"); + counter++; + } + }); + + Debug.WriteLine("Delay before release..."); + await Task.Delay(TimeSpan.FromSeconds(3.1)); + releaser.Dispose(); + + Debug.WriteLine("Wait for all tasks..."); + await Task.Delay(TimeSpan.FromSeconds(6.1)); + + Assert.AreEqual(3, counter); + } + + [TestMethod] + public void Lock_Parallel_Tasks() + { + const int taskCount = 50; + + var @lock = new AsyncLock(); - _ = Task.Run( + var tasks = new Task[taskCount]; + var globalI = 0; + for (var i = 0; i < taskCount; i++) + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + tasks[i] = Task.Run( async () => { - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(6))) + using (await @lock.EnterAsync()) { - using (await @lock.EnterAsync(timeout.Token)) - { - Debug.WriteLine("Task 6 incremented"); - counter++; - } + var localI = globalI; + await Task.Delay(5); // Increase the chance for wrong data. + localI++; + globalI = localI; } }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } - Debug.WriteLine("Delay before release..."); - await Task.Delay(TimeSpan.FromSeconds(3.1)); - releaser.Dispose(); - - Debug.WriteLine("Wait for all tasks..."); - await Task.Delay(TimeSpan.FromSeconds(6.1)); + Task.WaitAll(tasks); + Assert.AreEqual(taskCount, globalI); + } - Assert.AreEqual(3, counter); - } + [TestMethod] + public void Lock_10_Parallel_Tasks_With_Dispose_Doesnt_Lockup() + { + const int ThreadsCount = 10; - [TestMethod] - public void Lock_Parallel_Tasks() + var threads = new Task[ThreadsCount]; + var @lock = new AsyncLock(); + var globalI = 0; + for (var i = 0; i < ThreadsCount; i++) { - const int taskCount = 50; - - var @lock = new AsyncLock(); - - var tasks = new Task[taskCount]; - var globalI = 0; - for (var i = 0; i < taskCount; i++) - { #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - tasks[i] = Task.Run( + threads[i] = Task.Run( async () => { using (await @lock.EnterAsync()) { var localI = globalI; - await Task.Delay(5); // Increase the chance for wrong data. + await Task.Delay(10); // Increase the chance for wrong data. localI++; globalI = localI; } + }) + .ContinueWith( + x => + { + if (globalI == 5) + { + @lock.Dispose(); + @lock = new AsyncLock(); + } + + if (x.Exception != null) + { + Debug.WriteLine(x.Exception.GetBaseException().GetType().Name); + } }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - - Task.WaitAll(tasks); - Assert.AreEqual(taskCount, globalI); } - [TestMethod] - public void Lock_10_Parallel_Tasks_With_Dispose_Doesnt_Lockup() - { - const int ThreadsCount = 10; + Task.WaitAll(threads); - var threads = new Task[ThreadsCount]; - var @lock = new AsyncLock(); - var globalI = 0; - for (var i = 0; i < ThreadsCount; i++) - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - threads[i] = Task.Run( - async () => - { - using (await @lock.EnterAsync()) - { - var localI = globalI; - await Task.Delay(10); // Increase the chance for wrong data. - localI++; - globalI = localI; - } - }) - .ContinueWith( - x => - { - if (globalI == 5) - { - @lock.Dispose(); - @lock = new AsyncLock(); - } - - if (x.Exception != null) - { - Debug.WriteLine(x.Exception.GetBaseException().GetType().Name); - } - }); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - - Task.WaitAll(threads); + // Expect only 6 because the others are failing due to disposal (if (globalI == 5)). + Assert.AreEqual(6, globalI); + } - // Expect only 6 because the others are failing due to disposal (if (globalI == 5)). - Assert.AreEqual(6, globalI); - } + [TestMethod] + public async Task Lock_Serial_Calls() + { + var sum = 0; - [TestMethod] - public async Task Lock_Serial_Calls() + var @lock = new AsyncLock(); + for (var i = 0; i < 100; i++) { - var sum = 0; - - var @lock = new AsyncLock(); - for (var i = 0; i < 100; i++) + using (await @lock.EnterAsync().ConfigureAwait(false)) { - using (await @lock.EnterAsync().ConfigureAwait(false)) - { - sum++; - } + sum++; } - - Assert.AreEqual(100, sum); } - [TestMethod] - [ExpectedException(typeof(TaskCanceledException))] - public async Task Test_Cancellation() - { - var @lock = new AsyncLock(); + Assert.AreEqual(100, sum); + } - // This call will never "release" the lock due to missing _using_. - await @lock.EnterAsync().ConfigureAwait(false); + [TestMethod] + [ExpectedException(typeof(TaskCanceledException))] + public async Task Test_Cancellation() + { + var @lock = new AsyncLock(); - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - await @lock.EnterAsync(cts.Token).ConfigureAwait(false); - } - } + // This call will never "release" the lock due to missing _using_. + await @lock.EnterAsync().ConfigureAwait(false); - [TestMethod] - public async Task Test_Cancellation_With_Later_Access() - { - var asyncLock = new AsyncLock(); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + await @lock.EnterAsync(cts.Token).ConfigureAwait(false); + } - var releaser = await asyncLock.EnterAsync().ConfigureAwait(false); + [TestMethod] + public async Task Test_Cancellation_With_Later_Access() + { + var asyncLock = new AsyncLock(); - try - { - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - await asyncLock.EnterAsync(timeout.Token).ConfigureAwait(false); - } + var releaser = await asyncLock.EnterAsync().ConfigureAwait(false); - Assert.Fail("Exception should be thrown!"); - } - catch (OperationCanceledException) + try + { + using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3))) { + await asyncLock.EnterAsync(timeout.Token).ConfigureAwait(false); } - releaser.Dispose(); - - using (await asyncLock.EnterAsync(CancellationToken.None).ConfigureAwait(false)) - { - // When the method finished, the thread got access. - } + Assert.Fail("Exception should be thrown!"); + } + catch (OperationCanceledException) + { } - [TestMethod] - public async Task Use_After_Cancellation() + releaser.Dispose(); + + using (await asyncLock.EnterAsync(CancellationToken.None).ConfigureAwait(false)) { - var @lock = new AsyncLock(); + // When the method finished, the thread got access. + } + } - // This call will not yet "release" the lock due to missing _using_. - var releaser = await @lock.EnterAsync().ConfigureAwait(false); + [TestMethod] + public async Task Use_After_Cancellation() + { + var @lock = new AsyncLock(); - try - { - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - await @lock.EnterAsync(cts.Token).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - // Expected exception! - } + // This call will not yet "release" the lock due to missing _using_. + var releaser = await @lock.EnterAsync().ConfigureAwait(false); + + try + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + await @lock.EnterAsync(cts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // Expected exception! + } - releaser.Dispose(); + releaser.Dispose(); - // Regular usage after cancellation. - using (await @lock.EnterAsync().ConfigureAwait(false)) - { - } + // Regular usage after cancellation. + using (await @lock.EnterAsync().ConfigureAwait(false)) + { + } - using (await @lock.EnterAsync().ConfigureAwait(false)) - { - } + using (await @lock.EnterAsync().ConfigureAwait(false)) + { + } - using (await @lock.EnterAsync().ConfigureAwait(false)) - { - } + using (await @lock.EnterAsync().ConfigureAwait(false)) + { } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/AsyncQueue_Tests.cs b/Source/MQTTnet.Tests/Internal/AsyncQueue_Tests.cs index 91887eb64..33d756190 100644 --- a/Source/MQTTnet.Tests/Internal/AsyncQueue_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/AsyncQueue_Tests.cs @@ -8,138 +8,138 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public class AsyncQueue_Tests { - [TestClass] - public class AsyncQueue_Tests + [TestMethod] + public async Task Preserve_Order() { - [TestMethod] - public async Task Preserve_Order() - { - var queue = new AsyncQueue(); - queue.Enqueue("1"); - queue.Enqueue("2"); - queue.Enqueue("3"); - - Assert.AreEqual("1", (await queue.TryDequeueAsync(CancellationToken.None)).Item); - Assert.AreEqual("2", (await queue.TryDequeueAsync(CancellationToken.None)).Item); - Assert.AreEqual("3", (await queue.TryDequeueAsync(CancellationToken.None)).Item); - } + var queue = new AsyncQueue(); + queue.Enqueue("1"); + queue.Enqueue("2"); + queue.Enqueue("3"); + + Assert.AreEqual("1", (await queue.TryDequeueAsync(CancellationToken.None)).Item); + Assert.AreEqual("2", (await queue.TryDequeueAsync(CancellationToken.None)).Item); + Assert.AreEqual("3", (await queue.TryDequeueAsync(CancellationToken.None)).Item); + } - [TestMethod] - public void Count() - { - var queue = new AsyncQueue(); + [TestMethod] + public void Count() + { + var queue = new AsyncQueue(); - queue.Enqueue("1"); - Assert.AreEqual(1, queue.Count); + queue.Enqueue("1"); + Assert.AreEqual(1, queue.Count); - queue.Enqueue("2"); - Assert.AreEqual(2, queue.Count); + queue.Enqueue("2"); + Assert.AreEqual(2, queue.Count); - queue.Enqueue("3"); - Assert.AreEqual(3, queue.Count); - } + queue.Enqueue("3"); + Assert.AreEqual(3, queue.Count); + } - [TestMethod] - public async Task Cancellation() + [TestMethod] + public async Task Cancellation() + { + var queue = new AsyncQueue(); + + bool success; + using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3))) { - var queue = new AsyncQueue(); + success = (await queue.TryDequeueAsync(cancellationTokenSource.Token)).IsSuccess; + } - bool success; - using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - success = (await queue.TryDequeueAsync(cancellationTokenSource.Token)).IsSuccess; - } + Assert.AreEqual(false, success); + } - Assert.AreEqual(false, success); - } + [TestMethod] + public async Task Process_Async() + { + var queue = new AsyncQueue(); - [TestMethod] - public async Task Process_Async() + var sum = 0; + var worker = Task.Run(async () => { - var queue = new AsyncQueue(); - - var sum = 0; - var worker = Task.Run(async () => + while (sum < 6) { - while (sum < 6) - { - sum += (await queue.TryDequeueAsync(CancellationToken.None)).Item; - } - }); + sum += (await queue.TryDequeueAsync(CancellationToken.None)).Item; + } + }); - queue.Enqueue(1); - await Task.Delay(500); + queue.Enqueue(1); + await Task.Delay(500); - queue.Enqueue(2); - await Task.Delay(500); + queue.Enqueue(2); + await Task.Delay(500); - queue.Enqueue(3); - await Task.Delay(500); + queue.Enqueue(3); + await Task.Delay(500); - Assert.AreEqual(6, sum); - Assert.AreEqual(TaskStatus.RanToCompletion, worker.Status); - } + Assert.AreEqual(6, sum); + Assert.AreEqual(TaskStatus.RanToCompletion, worker.Status); + } - [TestMethod] - public async Task Process_Async_With_Initial_Delay() - { - var queue = new AsyncQueue(); + [TestMethod] + public async Task Process_Async_With_Initial_Delay() + { + var queue = new AsyncQueue(); - var sum = 0; - var worker = Task.Run(async () => + var sum = 0; + var worker = Task.Run(async () => + { + while (sum < 6) { - while (sum < 6) - { - sum += (await queue.TryDequeueAsync(CancellationToken.None)).Item; - } - }); + sum += (await queue.TryDequeueAsync(CancellationToken.None)).Item; + } + }); - // This line is the diff to test _Process_Async_ - await Task.Delay(500); + // This line is the diff to test _Process_Async_ + await Task.Delay(500); - queue.Enqueue(1); - await Task.Delay(500); + queue.Enqueue(1); + await Task.Delay(500); - queue.Enqueue(2); - await Task.Delay(500); + queue.Enqueue(2); + await Task.Delay(500); - queue.Enqueue(3); - await Task.Delay(500); + queue.Enqueue(3); + await Task.Delay(500); - Assert.AreEqual(6, sum); - Assert.AreEqual(TaskStatus.RanToCompletion, worker.Status); - } + Assert.AreEqual(6, sum); + Assert.AreEqual(TaskStatus.RanToCompletion, worker.Status); + } - [TestMethod] - public void Dequeue_Sync() - { - var queue = new AsyncQueue(); - queue.Enqueue("1"); - queue.Enqueue("2"); - queue.Enqueue("3"); - - Assert.AreEqual("1", queue.TryDequeue().Item); - Assert.AreEqual("2", queue.TryDequeue().Item); - Assert.AreEqual("3", queue.TryDequeue().Item); - } + [TestMethod] + public void Dequeue_Sync() + { + var queue = new AsyncQueue(); + queue.Enqueue("1"); + queue.Enqueue("2"); + queue.Enqueue("3"); + + Assert.AreEqual("1", queue.TryDequeue().Item); + Assert.AreEqual("2", queue.TryDequeue().Item); + Assert.AreEqual("3", queue.TryDequeue().Item); + } - [TestMethod] - public void Clear() - { - var queue = new AsyncQueue(); - queue.Enqueue("1"); - queue.Enqueue("2"); - queue.Enqueue("3"); + [TestMethod] + public void Clear() + { + var queue = new AsyncQueue(); + queue.Enqueue("1"); + queue.Enqueue("2"); + queue.Enqueue("3"); - queue.Clear(); - Assert.AreEqual(0, queue.Count); + queue.Clear(); + Assert.AreEqual(0, queue.Count); - queue.Enqueue("4"); + queue.Enqueue("4"); - Assert.AreEqual(1, queue.Count); - Assert.AreEqual("4", queue.TryDequeue().Item); - } + Assert.AreEqual(1, queue.Count); + Assert.AreEqual("4", queue.TryDequeue().Item); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs b/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs index 9adac5dc3..4b79934b3 100644 --- a/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/AsyncSignal_Tests.cs @@ -9,120 +9,114 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class AsyncSignal_Tests { - [TestClass] - public sealed class AsyncSignal_Tests + [TestMethod] + [ExpectedException(typeof(TaskCanceledException))] + public async Task Cancel_If_No_Signal() { - [TestMethod] - [ExpectedException(typeof(TaskCanceledException))] - public async Task Cancel_If_No_Signal() - { - var asyncSignal = new AsyncSignal(); + var asyncSignal = new AsyncSignal(); - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2))) - { - await asyncSignal.WaitAsync(timeout.Token); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); + await asyncSignal.WaitAsync(timeout.Token); - Assert.Fail("There is no signal. So we must fail here!"); - } - } + Assert.Fail("There is no signal. So we must fail here!"); + } - [TestMethod] - [ExpectedException(typeof(ObjectDisposedException))] - public async Task Dispose_Properly() - { - var asyncSignal = new AsyncSignal(); + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public async Task Dispose_Properly() + { + var asyncSignal = new AsyncSignal(); - // The timeout will not be reached but another task will kill the async signal. - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(999))) + // The timeout will not be reached but another task will kill the async signal. + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(999)); + _ = Task.Run( + async () => { - _ = Task.Run( - async () => - { - await Task.Delay(2000, CancellationToken.None); - asyncSignal.Dispose(); - }, - CancellationToken.None); + await Task.Delay(2000, CancellationToken.None); + asyncSignal.Dispose(); + }, + CancellationToken.None); - await asyncSignal.WaitAsync(timeout.Token); + await asyncSignal.WaitAsync(timeout.Token); - Assert.Fail("There is no signal. So we must fail here!"); - } - } + Assert.Fail("There is no signal. So we must fail here!"); + } - [TestMethod] - public async Task Reset_Signal() - { - var asyncSignal = new AsyncSignal(); + [TestMethod] + public async Task Reset_Signal() + { + var asyncSignal = new AsyncSignal(); - // WaitAsync should fail because no signal is available. - for (var i = 0; i < 10; i++) + // WaitAsync should fail because no signal is available. + for (var i = 0; i < 10; i++) + { + try { - try + using (var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100))) { - using (var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100))) - { - await asyncSignal.WaitAsync(timeout.Token); - } - - Assert.Fail("This must fail because the signal is not yet set."); + await asyncSignal.WaitAsync(timeout.Token); } - catch (OperationCanceledException) - { - } - - asyncSignal.Set(); - - // WaitAsync should return directly because the signal is available. - await asyncSignal.WaitAsync(); + + Assert.Fail("This must fail because the signal is not yet set."); } + catch (OperationCanceledException) + { + } + + asyncSignal.Set(); + + // WaitAsync should return directly because the signal is available. + await asyncSignal.WaitAsync(); } + } - [TestMethod] - public async Task Signal() - { - var asyncSignal = new AsyncSignal(); + [TestMethod] + public async Task Signal() + { + var asyncSignal = new AsyncSignal(); - // The timeout will not be reached but another task will kill the async signal. - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3))) + // The timeout will not be reached but another task will kill the async signal. + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)); + var stopwatch = Stopwatch.StartNew(); + + _ = Task.Run( + async () => { - var stopwatch = Stopwatch.StartNew(); + await Task.Delay(1000, CancellationToken.None); + asyncSignal.Set(); + }, + CancellationToken.None); - _ = Task.Run( - async () => - { - await Task.Delay(1000, CancellationToken.None); - asyncSignal.Set(); - }, - CancellationToken.None); + await asyncSignal.WaitAsync(timeout.Token); - await asyncSignal.WaitAsync(timeout.Token); + stopwatch.Stop(); - stopwatch.Stop(); + Assert.IsTrue(stopwatch.ElapsedMilliseconds > 900); + } - Assert.IsTrue(stopwatch.ElapsedMilliseconds > 900); - } - } + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public async Task Fail_For_Two_Waiters() + { + var asyncSignal = new AsyncSignal(); - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public async Task Fail_For_Two_Waiters() - { - var asyncSignal = new AsyncSignal(); + // This thread will wait properly because it is the first waiter. + _ = Task.Run( + async () => + { + await asyncSignal.WaitAsync().ConfigureAwait(false); + }, + CancellationToken.None); - // This thread will wait properly because it is the first waiter. - _ = Task.Run( - async () => - { - await asyncSignal.WaitAsync(); - }, - CancellationToken.None); + await Task.Delay(1000); - await Task.Delay(1000); - - // Now the current thread must fail because there is already a waiter. - await asyncSignal.WaitAsync(); - } + // Now the current thread must fail because there is already a waiter. + await asyncSignal.WaitAsync().ConfigureAwait(false); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/BlockingQueue_Tests.cs b/Source/MQTTnet.Tests/Internal/BlockingQueue_Tests.cs index 498370654..3175c98e3 100644 --- a/Source/MQTTnet.Tests/Internal/BlockingQueue_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/BlockingQueue_Tests.cs @@ -8,122 +8,122 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public class BlockingQueue_Tests { - [TestClass] - public class BlockingQueue_Tests + [TestMethod] + public void Preserve_Order() { - [TestMethod] - public void Preserve_Order() - { - var queue = new BlockingQueue(); - queue.Enqueue("a"); - queue.Enqueue("b"); - queue.Enqueue("c"); + var queue = new BlockingQueue(); + queue.Enqueue("a"); + queue.Enqueue("b"); + queue.Enqueue("c"); - Assert.AreEqual(3, queue.Count); + Assert.AreEqual(3, queue.Count); - Assert.AreEqual("a", queue.Dequeue()); - Assert.AreEqual("b", queue.Dequeue()); - Assert.AreEqual("c", queue.Dequeue()); - } + Assert.AreEqual("a", queue.Dequeue()); + Assert.AreEqual("b", queue.Dequeue()); + Assert.AreEqual("c", queue.Dequeue()); + } - [TestMethod] - public void Remove_First_Items() - { - var queue = new BlockingQueue(); - queue.Enqueue("a"); - queue.Enqueue("b"); - queue.Enqueue("c"); + [TestMethod] + public void Remove_First_Items() + { + var queue = new BlockingQueue(); + queue.Enqueue("a"); + queue.Enqueue("b"); + queue.Enqueue("c"); - Assert.AreEqual("a", queue.RemoveFirst()); - Assert.AreEqual("b", queue.RemoveFirst()); + Assert.AreEqual("a", queue.RemoveFirst()); + Assert.AreEqual("b", queue.RemoveFirst()); - Assert.AreEqual(1, queue.Count); + Assert.AreEqual(1, queue.Count); - Assert.AreEqual("c", queue.Dequeue()); - } + Assert.AreEqual("c", queue.Dequeue()); + } - [TestMethod] - public void Clear_Items() - { - var queue = new BlockingQueue(); - queue.Enqueue("a"); - queue.Enqueue("b"); - queue.Enqueue("c"); + [TestMethod] + public void Clear_Items() + { + var queue = new BlockingQueue(); + queue.Enqueue("a"); + queue.Enqueue("b"); + queue.Enqueue("c"); - Assert.AreEqual(3, queue.Count); + Assert.AreEqual(3, queue.Count); - queue.Clear(); + queue.Clear(); - Assert.AreEqual(0, queue.Count); - } + Assert.AreEqual(0, queue.Count); + } - [TestMethod] - public async Task Wait_For_Item() - { - var queue = new BlockingQueue(); + [TestMethod] + public async Task Wait_For_Item() + { + var queue = new BlockingQueue(); - string item = null; + string item = null; #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => - { - item = queue.Dequeue(); - }); + Task.Run(() => + { + item = queue.Dequeue(); + }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - await Task.Delay(100); + await Task.Delay(100); - Assert.AreEqual(0, queue.Count); - Assert.AreEqual(null, item); + Assert.AreEqual(0, queue.Count); + Assert.AreEqual(null, item); - queue.Enqueue("x"); + queue.Enqueue("x"); - await Task.Delay(100); + await Task.Delay(100); - Assert.AreEqual("x", item); - Assert.AreEqual(0, queue.Count); - } + Assert.AreEqual("x", item); + Assert.AreEqual(0, queue.Count); + } - [TestMethod] - public void Wait_For_Items() - { - var number = 0; + [TestMethod] + public void Wait_For_Items() + { + var number = 0; - var queue = new BlockingQueue(); + var queue = new BlockingQueue(); #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => + Task.Run(async () => + { + while (true) { - while (true) - { - queue.Enqueue(1); - await Task.Delay(100); - } - }); + queue.Enqueue(1); + await Task.Delay(100); + } + }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - while (number < 50) - { - queue.Dequeue(); - Interlocked.Increment(ref number); - } + while (number < 50) + { + queue.Dequeue(); + Interlocked.Increment(ref number); } + } - [TestMethod] - [ExpectedException(typeof(OperationCanceledException))] - public void Use_Disposed_Queue() - { - var queue = new BlockingQueue(); + [TestMethod] + [ExpectedException(typeof(OperationCanceledException))] + public void Use_Disposed_Queue() + { + var queue = new BlockingQueue(); #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => - { - Thread.Sleep(1000); - queue.Dispose(); - }); + Task.Run(() => + { + Thread.Sleep(1000); + queue.Dispose(); + }); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - queue.Dequeue(new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token); - } + queue.Dequeue(new CancellationTokenSource(TimeSpan.FromSeconds(2)).Token); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs b/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs index 7476ffea8..56e5fb49a 100644 --- a/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs @@ -11,71 +11,71 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Implementations; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public class CrossPlatformSocket_Tests { - [TestClass] - public class CrossPlatformSocket_Tests + [TestMethod] + public async Task Connect_Send_Receive() { - [TestMethod] - public async Task Connect_Send_Receive() - { - var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); - await crossPlatformSocket.ConnectAsync(new DnsEndPoint("www.google.de", 80), CancellationToken.None); - - var requestBuffer = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\nHost: www.google.de\r\n\r\n"); - await crossPlatformSocket.SendAsync(new ArraySegment(requestBuffer), System.Net.Sockets.SocketFlags.None); - - var buffer = new byte[1024]; - var length = await crossPlatformSocket.ReceiveAsync(new ArraySegment(buffer), System.Net.Sockets.SocketFlags.None); - crossPlatformSocket.Dispose(); - - var responseText = Encoding.UTF8.GetString(buffer, 0, length); - - Assert.IsTrue(responseText.Contains("HTTP/1.1 200 OK")); - } - - [TestMethod] - [ExpectedException(typeof(OperationCanceledException))] - public async Task Try_Connect_Invalid_Host() - { - var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); + var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); + await crossPlatformSocket.ConnectAsync(new DnsEndPoint("www.github.com", 80), CancellationToken.None); - var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - cancellationToken.Token.Register(() => crossPlatformSocket.Dispose()); + var requestBuffer = "GET / HTTP/1.1\r\nHost: www.github.com\r\n\r\n"u8.ToArray(); + await crossPlatformSocket.SendAsync(new ArraySegment(requestBuffer), SocketFlags.None); - await crossPlatformSocket.ConnectAsync(new DnsEndPoint("www.google.de", 54321), cancellationToken.Token); - } + var buffer = new byte[1024]; + var length = await crossPlatformSocket.ReceiveAsync(new ArraySegment(buffer), SocketFlags.None); + crossPlatformSocket.Dispose(); - //[TestMethod] - //public async Task Use_Disconnected_Socket() - //{ - // var crossPlatformSocket = new CrossPlatformSocket(); + var responseText = Encoding.UTF8.GetString(buffer, 0, length); - // await crossPlatformSocket.ConnectAsync("www.google.de", 80); - - // var requestBuffer = Encoding.UTF8.GetBytes("GET /wrong_uri HTTP/1.1\r\nConnection: close\r\n\r\n"); - // await crossPlatformSocket.SendAsync(new ArraySegment(requestBuffer), System.Net.Sockets.SocketFlags.None); + Assert.IsTrue(responseText.Contains("HTTP/1.1")); + } - // var buffer = new byte[64000]; - // var length = await crossPlatformSocket.ReceiveAsync(new ArraySegment(buffer), System.Net.Sockets.SocketFlags.None); + [TestMethod] + [ExpectedException(typeof(OperationCanceledException))] + public async Task Try_Connect_Invalid_Host() + { + var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); - // await Task.Delay(500); + var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + cancellationToken.Token.Register(() => crossPlatformSocket.Dispose()); - // await crossPlatformSocket.SendAsync(new ArraySegment(requestBuffer), System.Net.Sockets.SocketFlags.None); - //} + await crossPlatformSocket.ConnectAsync(new DnsEndPoint("www.github.com", 54321), cancellationToken.Token).ConfigureAwait(false); + } - [TestMethod] - public void Set_Options() - { - var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); + // [TestMethod] + // public async Task Use_Disconnected_Socket() + // { + // var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); + // + // await crossPlatformSocket.ConnectAsync("www.github.com", 80); + // + // var requestBuffer = Encoding.UTF8.GetBytes("GET /wrong_uri HTTP/1.1\r\nConnection: close\r\n\r\n"); + // await crossPlatformSocket.SendAsync(new ArraySegment(requestBuffer), System.Net.Sockets.SocketFlags.None); + // + // var buffer = new byte[64000]; + // var length = await crossPlatformSocket.ReceiveAsync(new ArraySegment(buffer), System.Net.Sockets.SocketFlags.None); + // + // await Task.Delay(500); + // + // await crossPlatformSocket.SendAsync(new ArraySegment(requestBuffer), System.Net.Sockets.SocketFlags.None); + // } + + [TestMethod] + public void Set_Options() + { + var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); - Assert.IsFalse(crossPlatformSocket.ReuseAddress); - crossPlatformSocket.ReuseAddress = true; - Assert.IsTrue(crossPlatformSocket.ReuseAddress); + Assert.IsFalse(crossPlatformSocket.ReuseAddress); + crossPlatformSocket.ReuseAddress = true; + Assert.IsTrue(crossPlatformSocket.ReuseAddress); - Assert.IsFalse(crossPlatformSocket.NoDelay); - crossPlatformSocket.NoDelay = true; - Assert.IsTrue(crossPlatformSocket.NoDelay); - } + Assert.IsFalse(crossPlatformSocket.NoDelay); + crossPlatformSocket.NoDelay = true; + Assert.IsTrue(crossPlatformSocket.NoDelay); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/MqttPacketBusItem_Tests.cs b/Source/MQTTnet.Tests/Internal/MqttPacketBusItem_Tests.cs index 912d38a0b..11bf45946 100644 --- a/Source/MQTTnet.Tests/Internal/MqttPacketBusItem_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/MqttPacketBusItem_Tests.cs @@ -7,37 +7,37 @@ using MQTTnet.Internal; using MQTTnet.Packets; -namespace MQTTnet.Tests.Internal +namespace MQTTnet.Tests.Internal; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttPacketBusItem_Tests { - [TestClass] - public sealed class MqttPacketBusItem_Tests + [TestMethod] + public void Fire_Completed_Event() { - [TestMethod] - public void Fire_Completed_Event() - { - var eventFired = false; + var eventFired = false; - var item = new MqttPacketBusItem(new MqttPublishPacket()); - item.Completed += (_, __) => - { - eventFired = true; - }; + var item = new MqttPacketBusItem(new MqttPublishPacket()); + item.Completed += (_, _) => + { + eventFired = true; + }; - item.Complete(); + item.Complete(); - Assert.IsTrue(eventFired); - } + Assert.IsTrue(eventFired); + } - [TestMethod] - [ExpectedException(typeof(TaskCanceledException))] - public async Task Wait_Packet_Bus_Item_After_Already_Canceled() - { - var item = new MqttPacketBusItem(new MqttPublishPacket()); + [TestMethod] + [ExpectedException(typeof(TaskCanceledException))] + public async Task Wait_Packet_Bus_Item_After_Already_Canceled() + { + var item = new MqttPacketBusItem(new MqttPublishPacket()); - // Finish the item before the actual - item.Cancel(); + // Finish the item before the actual + item.Cancel(); - await item.WaitAsync(); - } + await item.WaitAsync().ConfigureAwait(false); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Internal/MqttPacketBus_Tests.cs b/Source/MQTTnet.Tests/Internal/MqttPacketBus_Tests.cs index 62f547ef6..020b9d148 100644 --- a/Source/MQTTnet.Tests/Internal/MqttPacketBus_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/MqttPacketBus_Tests.cs @@ -3,149 +3,146 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; using MQTTnet.Packets; -namespace MQTTnet.Tests.Internal -{ - [TestClass] - public sealed class MqttPacketBus_Tests - { - [TestMethod] - public void Alternate_Priorities() - { - var bus = new MqttPacketBus(); - - bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); - bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); - bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); - - bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control); - bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control); - bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control); +namespace MQTTnet.Tests.Internal; - bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health); - bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health); - bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health); +[SuppressMessage("ReSharper", "InconsistentNaming")] +[TestClass] +public sealed class MqttPacketBus_Tests +{ + [TestMethod] + public void Alternate_Priorities() + { + var bus = new MqttPacketBus(); + + bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); + + bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control); + bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control); + bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control); + + bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health); + + Assert.AreEqual(9, bus.TotalItemsCount); + + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket)); + Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket)); + + Assert.AreEqual(0, bus.TotalItemsCount); + } - Assert.AreEqual(9, bus.TotalItemsCount); + [TestMethod] + public void Await_Single_Packet() + { + var bus = new MqttPacketBus(); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket)); - Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket)); + var delivered = false; - Assert.AreEqual(0, bus.TotalItemsCount); - } + var item1 = new MqttPacketBusItem(new MqttPublishPacket()); + var item2 = new MqttPacketBusItem(new MqttPublishPacket()); - [TestMethod] - public void Await_Single_Packet() + var item3 = new MqttPacketBusItem(new MqttPublishPacket()); + item3.Completed += (_, _) => { - var bus = new MqttPacketBus(); + delivered = true; + }; - var delivered = false; + bus.EnqueueItem(item1, MqttPacketBusPartition.Data); + bus.EnqueueItem(item2, MqttPacketBusPartition.Data); + bus.EnqueueItem(item3, MqttPacketBusPartition.Data); - var item1 = new MqttPacketBusItem(new MqttPublishPacket()); - var item2 = new MqttPacketBusItem(new MqttPublishPacket()); + Assert.IsFalse(delivered); - var item3 = new MqttPacketBusItem(new MqttPublishPacket()); - item3.Completed += (_, __) => - { - delivered = true; - }; - - bus.EnqueueItem(item1, MqttPacketBusPartition.Data); - bus.EnqueueItem(item2, MqttPacketBusPartition.Data); - bus.EnqueueItem(item3, MqttPacketBusPartition.Data); - - Assert.IsFalse(delivered); - - bus.DequeueItemAsync(CancellationToken.None).Result.Complete(); + bus.DequeueItemAsync(CancellationToken.None).Result.Complete(); - Assert.IsFalse(delivered); + Assert.IsFalse(delivered); - bus.DequeueItemAsync(CancellationToken.None).Result.Complete(); + bus.DequeueItemAsync(CancellationToken.None).Result.Complete(); - Assert.IsFalse(delivered); + Assert.IsFalse(delivered); - bus.DequeueItemAsync(CancellationToken.None).Result.Complete(); + bus.DequeueItemAsync(CancellationToken.None).Result.Complete(); - // The third packet has the event attached. - Assert.IsTrue(delivered); - } - - [TestMethod] - public void Export_Packets_Without_Dequeue() - { - var bus = new MqttPacketBus(); - - bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); - bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); - bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); + // The third packet has the event attached. + Assert.IsTrue(delivered); + } - Assert.AreEqual(3, bus.TotalItemsCount); + [TestMethod] + public void Export_Packets_Without_Dequeue() + { + var bus = new MqttPacketBus(); - var exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Control); - Assert.AreEqual(0, exportedPackets.Count); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); + bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data); - exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Health); - Assert.AreEqual(0, exportedPackets.Count); + Assert.AreEqual(3, bus.TotalItemsCount); - exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Data); - Assert.AreEqual(3, exportedPackets.Count); + var exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Control); + Assert.AreEqual(0, exportedPackets.Count); - Assert.AreEqual(3, bus.TotalItemsCount); - } + exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Health); + Assert.AreEqual(0, exportedPackets.Count); - [TestMethod] - public async Task Fill_From_Different_Task() - { - const int MessageCount = 500; + exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Data); + Assert.AreEqual(3, exportedPackets.Count); - var delayRandom = new Random(); + Assert.AreEqual(3, bus.TotalItemsCount); + } - var bus = new MqttPacketBus(); + [TestMethod] + public async Task Fill_From_Different_Task() + { + const int messageCount = 500; - _ = Task.Run( - () => - { - for (var i = 0; i < MessageCount; i++) - { - bus.EnqueueItem(new MqttPacketBusItem(MqttPingReqPacket.Instance), MqttPacketBusPartition.Health); + var delayRandom = new Random(); - Thread.Sleep(delayRandom.Next(0, 10)); - } - }); + var bus = new MqttPacketBus(); - for (var i = 0; i < MessageCount; i++) + _ = Task.Run( + () => { - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + for (var i = 0; i < messageCount; i++) { - await bus.DequeueItemAsync(timeout.Token); + bus.EnqueueItem(new MqttPacketBusItem(MqttPingReqPacket.Instance), MqttPacketBusPartition.Health); + + Thread.Sleep(delayRandom.Next(0, 10)); } - } + }); - Assert.AreEqual(0, bus.TotalItemsCount); + for (var i = 0; i < messageCount; i++) + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + await bus.DequeueItemAsync(timeout.Token); } - [TestMethod] - [ExpectedException(typeof(TaskCanceledException))] - public async Task Wait_With_Empty_Bus() - { - var bus = new MqttPacketBus(); + Assert.AreEqual(0, bus.TotalItemsCount); + } - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1))) - { - await bus.DequeueItemAsync(timeout.Token); - } - } + [TestMethod] + [ExpectedException(typeof(TaskCanceledException))] + public async Task Wait_With_Empty_Bus() + { + var bus = new MqttPacketBus(); + + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)); + await bus.DequeueItemAsync(timeout.Token); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj index 2641e0156..2b4e6acdc 100644 --- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj +++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj @@ -1,7 +1,7 @@ - + Exe net8.0 false @@ -18,9 +18,9 @@ - - - + + + diff --git a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs index 7b7570a99..e57e0a865 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.IO; @@ -13,370 +13,337 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Tests.MQTTv5 +namespace MQTTnet.Tests.MQTTv5; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +[TestClass] +public sealed class Client_Tests : BaseTestClass { - [TestClass] - public sealed class Client_Tests : BaseTestClass + [TestMethod] + public async Task Connect() { - [TestMethod] - public async Task Connect_With_New_Mqtt_Features() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - // This test can be also executed against "broker.hivemq.com" to validate package format. - var client = await testEnvironment.ConnectClient( - new MqttClientOptionsBuilder() - //.WithTcpServer("broker.hivemq.com") - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTopicAliasMaximum(20) - .WithReceiveMaximum(20) - .WithWillTopic("abc") - .WithWillDelayInterval(20) - .Build()); - - MqttApplicationMessage receivedMessage = null; - - await client.SubscribeAsync("a"); - client.ApplicationMessageReceivedAsync += e => - { - receivedMessage = e.ApplicationMessage; - return CompletedTask.Instance; - }; - - await client.PublishAsync(new MqttApplicationMessageBuilder() - .WithTopic("a") - .WithPayload("x") - .WithUserProperty("a", "1") - .WithUserProperty("b", "2") - .WithPayloadFormatIndicator(MqttPayloadFormatIndicator.CharacterData) - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .Build()); - - await Task.Delay(500); - - Assert.IsNotNull(receivedMessage); - - Assert.AreEqual(2, receivedMessage.UserProperties.Count); - } - } + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).Build()); + } - [TestMethod] - public async Task Connect() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).Build()); - } - } + [TestMethod] + public async Task Connect_And_Disconnect() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + await client.DisconnectAsync(); + } - [TestMethod] - public async Task Connect_And_Disconnect() + [TestMethod] + public async Task Connect_With_New_Mqtt_Features() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + // This test can be also executed against "broker.hivemq.com" to validate package format. + var client = await testEnvironment.ConnectClient( + new MqttClientOptionsBuilder() + //.WithTcpServer("broker.hivemq.com") + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTopicAliasMaximum(20) + .WithReceiveMaximum(20) + .WithWillTopic("abc") + .WithWillDelayInterval(20) + .Build()); + + MqttApplicationMessage receivedMessage = null; + + await client.SubscribeAsync("a"); + client.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + receivedMessage = e.ApplicationMessage; + return CompletedTask.Instance; + }; - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - await client.DisconnectAsync(); - } - } + await client.PublishAsync( + new MqttApplicationMessageBuilder().WithTopic("a") + .WithPayload("x") + .WithUserProperty("a", "1") + .WithUserProperty("b", "2") + .WithPayloadFormatIndicator(MqttPayloadFormatIndicator.CharacterData) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .Build()); - [TestMethod] - public async Task Subscribe() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + await Task.Delay(500); - var client = - await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + Assert.IsNotNull(receivedMessage); - var result = await client.SubscribeAsync(new MqttClientSubscribeOptions() - { - SubscriptionIdentifier = 1, - TopicFilters = new List - { - new MqttTopicFilter {Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce} - } - }); + Assert.AreEqual(2, receivedMessage.UserProperties.Count); + } - await client.DisconnectAsync(); + [TestMethod] + public async Task Publish_And_Receive_New_Properties() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - Assert.AreEqual(1, result.Items.Count); - Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items.First().ResultCode); - } - } + var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + await receiver.SubscribeAsync("#"); - [TestMethod] - public async Task Unsubscribe() + MqttApplicationMessage receivedMessage = null; + receiver.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + receivedMessage = e.ApplicationMessage; + return CompletedTask.Instance; + }; + + var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + + var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("Hello") + .WithPayload("World") + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) + .WithUserProperty("x", "1") + .WithUserProperty("y", "2") + .WithResponseTopic("response") + .WithContentType("text") + .WithMessageExpiryInterval(50) + .WithCorrelationData(new byte[12]) + .WithTopicAlias(2) + .Build(); + + await sender.PublishAsync(applicationMessage); + + await Task.Delay(500); + + Assert.IsNotNull(receivedMessage); + Assert.AreEqual(applicationMessage.Topic, receivedMessage.Topic); + Assert.AreEqual(applicationMessage.TopicAlias, receivedMessage.TopicAlias); + Assert.AreEqual(applicationMessage.ContentType, receivedMessage.ContentType); + Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); + Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); + CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); + CollectionAssert.AreEqual(applicationMessage.Payload.ToArray(), receivedMessage.Payload.ToArray()); + CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); + } - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - await client.SubscribeAsync("a"); + [TestMethod] + public async Task Publish_QoS_0() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var result = await client.UnsubscribeAsync("a"); - await client.DisconnectAsync(); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var result = await client.PublishStringAsync("a", "b"); + await client.DisconnectAsync(); - Assert.AreEqual(1, result.Items.Count); - Assert.AreEqual(MqttClientUnsubscribeResultCode.Success, result.Items.First().ResultCode); - } - } + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } - [TestMethod] - public async Task Publish_QoS_0_LargeBuffer() - { - using var recyclableMemoryStream = GetLargePayload(); - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_QoS_0_LargeBuffer() + { + await using var recyclableMemoryStream = GetLargePayload(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var result = await client.PublishSequenceAsync("a", recyclableMemoryStream.GetReadOnlySequence()); - await client.DisconnectAsync(); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var result = await client.PublishSequenceAsync("a", recyclableMemoryStream.GetReadOnlySequence()); + await client.DisconnectAsync(); - Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); - } - } + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } - [TestMethod] - public async Task Publish_QoS_1_LargeBuffer() - { - using var recyclableMemoryStream = GetLargePayload(); - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_QoS_1() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var result = await client.PublishSequenceAsync("a", recyclableMemoryStream.GetReadOnlySequence(), MqttQualityOfServiceLevel.AtLeastOnce); - await client.DisconnectAsync(); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce); + await client.DisconnectAsync(); - Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); - } - } + Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); + } - [TestMethod] - public async Task Publish_QoS_2_LargeBuffer() - { - using var recyclableMemoryStream = GetLargePayload(); - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_QoS_1_LargeBuffer() + { + await using var recyclableMemoryStream = GetLargePayload(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var result = await client.PublishSequenceAsync("a", recyclableMemoryStream.GetReadOnlySequence(), MqttQualityOfServiceLevel.ExactlyOnce); - await client.DisconnectAsync(); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var result = await client.PublishSequenceAsync("a", recyclableMemoryStream.GetReadOnlySequence(), MqttQualityOfServiceLevel.AtLeastOnce); + await client.DisconnectAsync(); - Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); - } - } + Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); + } - [TestMethod] - public async Task Publish_QoS_0() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_QoS_2() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var result = await client.PublishStringAsync("a", "b"); - await client.DisconnectAsync(); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce); + await client.DisconnectAsync(); - Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); - } - } + Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); + } - [TestMethod] - public async Task Publish_QoS_1() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_QoS_2_LargeBuffer() + { + await using var recyclableMemoryStream = GetLargePayload(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce); - await client.DisconnectAsync(); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var result = await client.PublishSequenceAsync("a", recyclableMemoryStream.GetReadOnlySequence(), MqttQualityOfServiceLevel.ExactlyOnce); + await client.DisconnectAsync(); - Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); - } - } + Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); + } - [TestMethod] - public async Task Publish_QoS_2() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_With_Properties() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + + var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("Hello") + .WithPayload("World") + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) + .WithUserProperty("x", "1") + .WithUserProperty("y", "2") + .WithResponseTopic("response") + .WithContentType("text") + .WithMessageExpiryInterval(50) + .WithCorrelationData(new byte[12]) + .WithTopicAlias(2) + .Build(); + + var result = await client.PublishAsync(applicationMessage); + await client.DisconnectAsync(); + + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce); - await client.DisconnectAsync(); + [TestMethod] + public async Task Publish_With_RecyclableMemoryStream() + { + var memoryManager = new RecyclableMemoryStreamManager(new RecyclableMemoryStreamManager.Options { BlockSize = 4096 }); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); - } - } + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - [TestMethod] - public async Task Publish_With_RecyclableMemoryStream() - { - var memoryManager = new RecyclableMemoryStreamManager(options: new RecyclableMemoryStreamManager.Options { BlockSize = 4096 }); - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - - const int payloadSize = 100000; - using var memoryStream = memoryManager.GetStream(); - - byte testValue = 0; - while (memoryStream.Length < payloadSize) - { - memoryStream.WriteByte(testValue++); - } - - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("Hello") - .WithPayload(memoryStream.GetReadOnlySequence()) - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) - .WithUserProperty("x", "1") - .WithUserProperty("y", "2") - .WithResponseTopic("response") - .WithContentType("text") - .WithMessageExpiryInterval(50) - .WithCorrelationData(new byte[12]) - .WithTopicAlias(2) - .Build(); - - var result = await client.PublishAsync(applicationMessage); - await client.DisconnectAsync(); - - Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); - } - } + const int payloadSize = 100000; + await using var memoryStream = memoryManager.GetStream(); - [TestMethod] - public async Task Publish_With_Properties() + byte testValue = 0; + while (memoryStream.Length < payloadSize) { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("Hello") - .WithPayload("World") - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) - .WithUserProperty("x", "1") - .WithUserProperty("y", "2") - .WithResponseTopic("response") - .WithContentType("text") - .WithMessageExpiryInterval(50) - .WithCorrelationData(new byte[12]) - .WithTopicAlias(2) - .Build(); - - var result = await client.PublishAsync(applicationMessage); - await client.DisconnectAsync(); - - Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); - } + memoryStream.WriteByte(testValue++); } - [TestMethod] - public async Task Subscribe_And_Publish() - { - using (var testEnvironment = CreateTestEnvironment()) + var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("Hello") + .WithPayload(memoryStream.GetReadOnlySequence()) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) + .WithUserProperty("x", "1") + .WithUserProperty("y", "2") + .WithResponseTopic("response") + .WithContentType("text") + .WithMessageExpiryInterval(50) + .WithCorrelationData(new byte[12]) + .WithTopicAlias(2) + .Build(); + + var result = await client.PublishAsync(applicationMessage); + await client.DisconnectAsync(); + + Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode); + } + + [TestMethod] + public async Task Subscribe() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + + var result = await client.SubscribeAsync( + new MqttClientSubscribeOptions { - await testEnvironment.StartServer(); + SubscriptionIdentifier = 1, + TopicFilters = + [ + new MqttTopicFilter() { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce } + ] + }); - var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1")); + await client.DisconnectAsync(); - var testMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + Assert.AreEqual(1, result.Items.Count); + Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items.First().ResultCode); + } - await client1.SubscribeAsync("a"); + [TestMethod] + public async Task Subscribe_And_Publish() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client2")); - await client2.PublishStringAsync("a", "b"); + var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1")); - await Task.Delay(500); + var testMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); - await client2.DisconnectAsync(); - await client1.DisconnectAsync(); + await client1.SubscribeAsync("a"); - Assert.AreEqual(1, testMessageHandler.ReceivedEventArgs.Count); - Assert.AreEqual("Subscribe_And_Publish_client1", testMessageHandler.ReceivedEventArgs[0].ClientId); - Assert.AreEqual("a", testMessageHandler.ReceivedEventArgs[0].ApplicationMessage.Topic); - Assert.AreEqual("b", testMessageHandler.ReceivedEventArgs[0].ApplicationMessage.ConvertPayloadToString()); - } - } + var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client2")); + await client2.PublishStringAsync("a", "b"); - [TestMethod] - public async Task Publish_And_Receive_New_Properties() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - await receiver.SubscribeAsync("#"); - - MqttApplicationMessage receivedMessage = null; - receiver.ApplicationMessageReceivedAsync += e => - { - receivedMessage = e.ApplicationMessage; - return CompletedTask.Instance; - }; - - var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("Hello") - .WithPayload("World") - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) - .WithUserProperty("x", "1") - .WithUserProperty("y", "2") - .WithResponseTopic("response") - .WithContentType("text") - .WithMessageExpiryInterval(50) - .WithCorrelationData(new byte[12]) - .WithTopicAlias(2) - .Build(); - - await sender.PublishAsync(applicationMessage); - - await Task.Delay(500); - - Assert.IsNotNull(receivedMessage); - Assert.AreEqual(applicationMessage.Topic, receivedMessage.Topic); - Assert.AreEqual(applicationMessage.TopicAlias, receivedMessage.TopicAlias); - Assert.AreEqual(applicationMessage.ContentType, receivedMessage.ContentType); - Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); - Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); - CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); - CollectionAssert.AreEqual(applicationMessage.Payload.ToArray(), receivedMessage.Payload.ToArray()); - CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); - } - } + await Task.Delay(500); - private RecyclableMemoryStream GetLargePayload() - { - var memoryManager = new RecyclableMemoryStreamManager(); - var memoryStream = memoryManager.GetStream(); - for (var i = 0; i < 100000; i++) - { - memoryStream.WriteByte((byte)i); - } + await client2.DisconnectAsync(); + await client1.DisconnectAsync(); - memoryStream.Position = 0; - return memoryStream; + Assert.AreEqual(1, testMessageHandler.ReceivedEventArgs.Count); + Assert.AreEqual("Subscribe_And_Publish_client1", testMessageHandler.ReceivedEventArgs[0].ClientId); + Assert.AreEqual("a", testMessageHandler.ReceivedEventArgs[0].ApplicationMessage.Topic); + Assert.AreEqual("b", testMessageHandler.ReceivedEventArgs[0].ApplicationMessage.ConvertPayloadToString()); + } + + [TestMethod] + public async Task Unsubscribe() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + await client.SubscribeAsync("a"); + + var result = await client.UnsubscribeAsync("a"); + await client.DisconnectAsync(); + + Assert.AreEqual(1, result.Items.Count); + Assert.AreEqual(MqttClientUnsubscribeResultCode.Success, result.Items.First().ResultCode); + } + + static RecyclableMemoryStream GetLargePayload() + { + var memoryManager = new RecyclableMemoryStreamManager(); + var memoryStream = memoryManager.GetStream(); + for (var i = 0; i < 100000; i++) + { + memoryStream.WriteByte((byte)i); } + + memoryStream.Position = 0; + return memoryStream; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs index 067fed791..df3060e13 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs @@ -11,221 +11,210 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.MQTTv5 +namespace MQTTnet.Tests.MQTTv5; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Server_Tests : BaseTestClass { - [TestClass] - public sealed class Server_Tests : BaseTestClass + [TestMethod] + public async Task Will_Message_Send() { - [TestMethod] - public async Task Will_Message_Send() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce).WithProtocolVersion(MqttProtocolVersion.V500); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce).WithProtocolVersion(MqttProtocolVersion.V500); - var receivedMessagesCount = 0; - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); + var receivedMessagesCount = 0; + c1.ApplicationMessageReceivedAsync += _ => + { + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - var c2 = await testEnvironment.ConnectClient(clientOptions); - c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent. + await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); - await LongTestDelay(); + var c2 = await testEnvironment.ConnectClient(clientOptions); + c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent. - Assert.AreEqual(1, receivedMessagesCount); - } - } + await LongTestDelay(); - [TestMethod] - public async Task Validate_IsSessionPresent() - { - using (var testEnvironment = CreateTestEnvironment()) - { - // Create server with persistent sessions enabled + Assert.AreEqual(1, receivedMessagesCount); + } - await testEnvironment.StartServer(o => o.WithPersistentSessions()); + [TestMethod] + public async Task Validate_IsSessionPresent() + { + using var testEnvironment = CreateTestEnvironment(); + // Create server with persistent sessions enabled - const string ClientId = "Client1"; + await testEnvironment.StartServer(o => o.WithPersistentSessions()); - // Create client without clean session and long session expiry interval - var options1 = CreateClientOptions(testEnvironment, ClientId, false, 9999); - var client1 = await testEnvironment.ConnectClient(options1); + const string clientId = "Client1"; - // Disconnect; empty session should remain on server + // Create client without clean session and long session expiry interval + var options1 = CreateClientOptions(testEnvironment, clientId, false, 9999); + var client1 = await testEnvironment.ConnectClient(options1); - await client1.DisconnectAsync(); + // Disconnect; empty session should remain on server - // Simulate some time delay between connections + await client1.DisconnectAsync(); - await Task.Delay(1000); + // Simulate some time delay between connections - // Reconnect the same client ID to existing session + await Task.Delay(1000); - var client2 = testEnvironment.CreateClient(); - var options2 = CreateClientOptions(testEnvironment, ClientId, false, 9999); - var result = await client2.ConnectAsync(options2).ConfigureAwait(false); + // Reconnect the same client ID to existing session - await client2.DisconnectAsync(); + var client2 = testEnvironment.CreateClient(); + var options2 = CreateClientOptions(testEnvironment, clientId, false, 9999); + var result = await client2.ConnectAsync(options2).ConfigureAwait(false); - // Session should be present + await client2.DisconnectAsync(); - Assert.IsTrue(result.IsSessionPresent, "Session not present"); - } - } + // Session should be present - [TestMethod] - public async Task Connect_with_Undefined_SessionExpiryInterval() - { - using (var testEnvironment = CreateTestEnvironment()) - { - // Create server with persistent sessions enabled + Assert.IsTrue(result.IsSessionPresent, "Session not present"); + } - await testEnvironment.StartServer(o => o.WithPersistentSessions()); + [TestMethod] + public async Task Connect_with_Undefined_SessionExpiryInterval() + { + using var testEnvironment = CreateTestEnvironment(); + // Create server with persistent sessions enabled - const string ClientId = "Client1"; + await testEnvironment.StartServer(o => o.WithPersistentSessions()); - // Create client without clean session and NO session expiry interval, - // that means, the session should not persist + const string clientId = "Client1"; - var options1 = CreateClientOptions(testEnvironment, ClientId, false, 0); - var client1 = await testEnvironment.ConnectClient(options1); + // Create client without clean session and NO session expiry interval, + // that means, the session should not persist - // Disconnect; no session should remain on server because the session expiry interval was undefined + var options1 = CreateClientOptions(testEnvironment, clientId, false, 0); + var client1 = await testEnvironment.ConnectClient(options1); - await client1.DisconnectAsync(); + // Disconnect; no session should remain on server because the session expiry interval was undefined - // Simulate some time delay between connections + await client1.DisconnectAsync(); - await Task.Delay(1000); + // Simulate some time delay between connections - // Reconnect the same client ID to existing session + await Task.Delay(1000); - var client2 = testEnvironment.CreateClient(); - var options2 = CreateClientOptions(testEnvironment, ClientId, false, 9999); - var result = await client2.ConnectAsync(options2).ConfigureAwait(false); + // Reconnect the same client ID to existing session - await client2.DisconnectAsync(); + var client2 = testEnvironment.CreateClient(); + var options2 = CreateClientOptions(testEnvironment, clientId, false, 9999); + var result = await client2.ConnectAsync(options2).ConfigureAwait(false); - // Session should not be present + await client2.DisconnectAsync(); - Assert.IsTrue(!result.IsSessionPresent, "Session is present when it should not"); - } - } + // Session should not be present + Assert.IsTrue(!result.IsSessionPresent, "Session is present when it should not"); + } - [TestMethod] - public async Task Reconnect_with_different_SessionExpiryInterval() - { - using (var testEnvironment = CreateTestEnvironment()) - { - // Create server with persistent sessions enabled + [TestMethod] + public async Task Reconnect_with_different_SessionExpiryInterval() + { + using var testEnvironment = CreateTestEnvironment(); + // Create server with persistent sessions enabled - await testEnvironment.StartServer(o => o.WithPersistentSessions()); + await testEnvironment.StartServer(o => o.WithPersistentSessions()); - const string ClientId = "Client1"; + const string clientId = "Client1"; - // Create client with clean session and session expiry interval > 0 + // Create client with clean session and session expiry interval > 0 - var options = CreateClientOptions(testEnvironment, ClientId, true, 9999); - var client1 = await testEnvironment.ConnectClient(options); + var options = CreateClientOptions(testEnvironment, clientId, true, 9999); + var client1 = await testEnvironment.ConnectClient(options); - // Disconnect; session should remain on server + // Disconnect; session should remain on server - await client1.DisconnectAsync(); + await client1.DisconnectAsync(); - await Task.Delay(1000); + await Task.Delay(1000); - // Reconnect the same client ID to the existing session but leave session expiry interval undefined this time. - // Session should be present because the client1 connection had SessionExpiryInterval > 0 + // Reconnect the same client ID to the existing session but leave session expiry interval undefined this time. + // Session should be present because the client1 connection had SessionExpiryInterval > 0 - var client2 = testEnvironment.CreateClient(); - var options2 = CreateClientOptions(testEnvironment, ClientId, false, 0); - var result2 = await client2.ConnectAsync(options2).ConfigureAwait(false); + var client2 = testEnvironment.CreateClient(); + var options2 = CreateClientOptions(testEnvironment, clientId, false, 0); + var result2 = await client2.ConnectAsync(options2).ConfigureAwait(false); - await client2.DisconnectAsync(); + await client2.DisconnectAsync(); - Assert.IsTrue(result2.IsSessionPresent, "Session is not present when it should"); + Assert.IsTrue(result2.IsSessionPresent, "Session is not present when it should"); - // Simulate some time delay between connections + // Simulate some time delay between connections - await Task.Delay(1000); + await Task.Delay(1000); - // Reconnect the same client ID. - // No session should be present because the previous session expiry interval was undefined for the client2 connection + // Reconnect the same client ID. + // No session should be present because the previous session expiry interval was undefined for the client2 connection - var client3 = testEnvironment.CreateClient(); - var options3 = CreateClientOptions(testEnvironment, ClientId, false, 0); - var result3 = await client2.ConnectAsync(options3).ConfigureAwait(false); + var client3 = testEnvironment.CreateClient(); + var options3 = CreateClientOptions(testEnvironment, clientId, false, 0); + var result3 = await client2.ConnectAsync(options3).ConfigureAwait(false); - await client3.DisconnectAsync(); + await client3.DisconnectAsync(); - // Session should be present + // Session should be present - Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not"); - } - } + Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not"); + } - [TestMethod] - public async Task Disconnect_with_Reason() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var disconnectReason = MqttClientDisconnectReason.UnspecifiedError; + [TestMethod] + public async Task Disconnect_with_Reason() + { + using var testEnvironment = CreateTestEnvironment(); + var disconnectReason = MqttClientDisconnectReason.UnspecifiedError; - string testClientId = null; + string testClientId = null; - await testEnvironment.StartServer(); + await testEnvironment.StartServer(); - testEnvironment.Server.ClientConnectedAsync += e => - { - testClientId = e.ClientId; - return CompletedTask.Instance; - }; + testEnvironment.Server.ClientConnectedAsync += e => + { + testClientId = e.ClientId; + return CompletedTask.Instance; + }; - var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - client.DisconnectedAsync += e => - { - disconnectReason = e.Reason; - return CompletedTask.Instance; - }; + client.DisconnectedAsync += e => + { + disconnectReason = e.Reason; + return CompletedTask.Instance; + }; - await LongTestDelay(); + await LongTestDelay(); - // Test client should be connected now + // Test client should be connected now - Assert.IsTrue(testClientId != null); + Assert.IsTrue(testClientId != null); - // Have the server disconnect the client with AdministrativeAction reason + // Have the server disconnect the client with AdministrativeAction reason - await testEnvironment.Server.DisconnectClientAsync(testClientId, MqttDisconnectReasonCode.AdministrativeAction); + await testEnvironment.Server.DisconnectClientAsync(testClientId, MqttDisconnectReasonCode.AdministrativeAction); - await LongTestDelay(); + await LongTestDelay(); - // The reason should be returned to the client in the DISCONNECT packet + // The reason should be returned to the client in the DISCONNECT packet - Assert.AreEqual(MqttClientDisconnectReason.AdministrativeAction, disconnectReason); - } - } + Assert.AreEqual(MqttClientDisconnectReason.AdministrativeAction, disconnectReason); + } - static MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval) - { - return testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .WithSessionExpiryInterval(sessionExpiryInterval) - .WithCleanSession(cleanSession) - .WithClientId(clientId) - .Build(); - } + static MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval) + { + return testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithSessionExpiryInterval(sessionExpiryInterval) + .WithCleanSession(cleanSession) + .WithClientId(clientId) + .Build(); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs b/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs index 20c76602b..6d3056867 100644 --- a/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs +++ b/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.AspNetCore.Http; using MQTTnet.Channel; using MQTTnet.Internal; using System.Buffers; @@ -12,53 +11,52 @@ using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.Tests.Mockups +namespace MQTTnet.Tests.Mockups; + +public sealed class MemoryMqttChannel : IMqttChannel { - public sealed class MemoryMqttChannel : IMqttChannel - { - readonly MemoryStream _stream; + readonly MemoryStream _stream; - public MemoryMqttChannel(MemoryStream stream) - { - _stream = stream; - } + public MemoryMqttChannel(MemoryStream stream) + { + _stream = stream; + } - public MemoryMqttChannel(byte[] buffer) - { - _stream = new MemoryStream(buffer); - } + public MemoryMqttChannel(byte[] buffer) + { + _stream = new MemoryStream(buffer); + } - public EndPoint RemoteEndPoint { get; set; } + public EndPoint RemoteEndPoint { get; set; } - public bool IsSecureConnection { get; } = false; + public bool IsSecureConnection { get; } = false; - public X509Certificate2 ClientCertificate { get; } + public X509Certificate2 ClientCertificate { get; set; } - public Task ConnectAsync(CancellationToken cancellationToken) - { - return CompletedTask.Instance; - } + public Task ConnectAsync(CancellationToken cancellationToken) + { + return CompletedTask.Instance; + } - public Task DisconnectAsync(CancellationToken cancellationToken) - { - return CompletedTask.Instance; - } + public Task DisconnectAsync(CancellationToken cancellationToken) + { + return CompletedTask.Instance; + } - public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _stream.ReadAsync(buffer, offset, count, cancellationToken); - } + public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _stream.ReadAsync(buffer, offset, count, cancellationToken); + } - public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + foreach (var segment in buffer) { - foreach (var segment in buffer) - { - await _stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false); - } + await _stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false); } + } - public void Dispose() - { - } + public void Dispose() + { } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs index 66b44bd16..d806322d8 100644 --- a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs +++ b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs @@ -10,72 +10,71 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; -namespace MQTTnet.Tests.Mockups +namespace MQTTnet.Tests.Mockups; + +public sealed class TestApplicationMessageReceivedHandler { - public sealed class TestApplicationMessageReceivedHandler - { - readonly List _receivedEventArgs = new List(); + readonly List _receivedEventArgs = new List(); - public TestApplicationMessageReceivedHandler(IMqttClient mqttClient) - { - ArgumentNullException.ThrowIfNull(mqttClient); + public TestApplicationMessageReceivedHandler(IMqttClient mqttClient) + { + ArgumentNullException.ThrowIfNull(mqttClient); - mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync; - } + mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync; + } - public int Count + public int Count + { + get { - get + lock (_receivedEventArgs) { - lock (_receivedEventArgs) - { - return _receivedEventArgs.Count; - } + return _receivedEventArgs.Count; } } + } - public List ReceivedEventArgs + public List ReceivedEventArgs + { + get { - get + lock (_receivedEventArgs) { - lock (_receivedEventArgs) - { - return _receivedEventArgs.ToList(); - } + return _receivedEventArgs.ToList(); } } + } - public void AssertReceivedCountEquals(int expectedCount) - { - Assert.AreEqual(expectedCount, Count); - } + public void AssertReceivedCountEquals(int expectedCount) + { + Assert.AreEqual(expectedCount, Count); + } - public string GeneratePayloadSequence() - { - var sequence = new StringBuilder(); + public string GeneratePayloadSequence() + { + var sequence = new StringBuilder(); - lock (_receivedEventArgs) + lock (_receivedEventArgs) + { + foreach (var receivedEventArg in _receivedEventArgs) { - foreach (var receivedEventArg in _receivedEventArgs) - { - var payload = receivedEventArg.ApplicationMessage.ConvertPayloadToString(); + var payload = receivedEventArg.ApplicationMessage.ConvertPayloadToString(); - // An empty payload is not part of the sequence! - sequence.Append(payload); - } + // An empty payload is not part of the sequence! + sequence.Append(payload); } - - return sequence.ToString(); } - Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) - { - lock (_receivedEventArgs) - { - _receivedEventArgs.Add(eventArgs); - } + return sequence.ToString(); + } - return CompletedTask.Instance; + Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + { + lock (_receivedEventArgs) + { + _receivedEventArgs.Add(eventArgs); } + + return CompletedTask.Instance; } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs b/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs index 6f2d8ae73..c0083f93b 100644 --- a/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs +++ b/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs @@ -17,422 +17,410 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Mockups +namespace MQTTnet.Tests.Mockups; + +public sealed class TestEnvironment : IDisposable { - public sealed class TestEnvironment : IDisposable + readonly List _clientErrors = new(); + readonly List _clients = new(); + readonly List _exceptions = new(); + readonly List _lowLevelClients = new(); + readonly MqttProtocolVersion _protocolVersion; + readonly List _serverErrors = new(); + + public TestEnvironment() : this(null) + { + } + + public TestEnvironment( + TestContext testContext, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, bool trackUnobservedTaskException = true) { - readonly List _clientErrors = new(); - readonly List _clients = new(); - readonly List _exceptions = new(); - readonly List _lowLevelClients = new(); - readonly MqttProtocolVersion _protocolVersion; - readonly List _serverErrors = new(); - - public TestEnvironment() : this(null) + _protocolVersion = protocolVersion; + TestContext = testContext; + + if (trackUnobservedTaskException) { + TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException; } - public TestEnvironment( - TestContext testContext, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, bool trackUnobservedTaskException = true) + ServerLogger.LogMessagePublished += (_, e) => { - _protocolVersion = protocolVersion; - TestContext = testContext; + Debug.WriteLine(e.LogMessage.ToString()); - if (trackUnobservedTaskException) + if (e.LogMessage.Level == MqttNetLogLevel.Error) { - TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException; - } - - ServerLogger.LogMessagePublished += (s, e) => - { - if (Debugger.IsAttached) + lock (_serverErrors) { - Debug.WriteLine(e.LogMessage.ToString()); + _serverErrors.Add(e.LogMessage.ToString()); } + } + }; - if (e.LogMessage.Level == MqttNetLogLevel.Error) - { - lock (_serverErrors) - { - _serverErrors.Add(e.LogMessage.ToString()); - } - } - }; + ClientLogger.LogMessagePublished += (_, e) => + { + Debug.WriteLine(e.LogMessage.ToString()); - ClientLogger.LogMessagePublished += (s, e) => + if (e.LogMessage.Level == MqttNetLogLevel.Error) { - if (Debugger.IsAttached) + if (!IgnoreClientLogErrors) { - Debug.WriteLine(e.LogMessage.ToString()); - } - - if (e.LogMessage.Level == MqttNetLogLevel.Error) - { - if (!IgnoreClientLogErrors) + lock (_clientErrors) { - lock (_clientErrors) - { - _clientErrors.Add(e.LogMessage.ToString()); - } + _clientErrors.Add(e.LogMessage.ToString()); } } - }; - } - - public MqttNetEventLogger ClientLogger { get; } = new("client"); + } + }; + } - public static bool EnableLogger { get; set; } = true; + public MqttNetEventLogger ClientLogger { get; } = new("client"); - public MqttClientFactory ClientFactory { get; } = new(); + public static bool EnableLogger { get; set; } = true; - public MqttServerFactory ServerFactory { get; } = new(); + public MqttClientFactory ClientFactory { get; } = new(); - public bool IgnoreClientLogErrors { get; set; } + public MqttServerFactory ServerFactory { get; } = new(); - public bool IgnoreServerLogErrors { get; set; } + public bool IgnoreClientLogErrors { get; set; } - public MqttServer Server { get; private set; } + public bool IgnoreServerLogErrors { get; set; } - public MqttNetEventLogger ServerLogger { get; } = new("server"); + public MqttServer Server { get; private set; } - public int ServerPort { get; set; } + public MqttNetEventLogger ServerLogger { get; } = new("server"); - public TestContext TestContext { get; } + public int ServerPort { get; set; } - public Task ConnectClient() - { - return ConnectClient(ClientFactory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion)); - } + public TestContext TestContext { get; } - public async Task ConnectClient(Action configureOptions, TimeSpan timeout = default) - { - ArgumentNullException.ThrowIfNull(configureOptions); + public Task ConnectClient() + { + return ConnectClient(ClientFactory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion)); + } - // Start with initial default values. - var optionsBuilder = ClientFactory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort); + public async Task ConnectClient(Action configureOptions, TimeSpan timeout = default) + { + ArgumentNullException.ThrowIfNull(configureOptions); - // Let the caller override settings. Do not touch the options after this. - configureOptions.Invoke(optionsBuilder); + // Start with initial default values. + var optionsBuilder = ClientFactory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort); - var options = optionsBuilder.Build(); + // Let the caller override settings. Do not touch the options after this. + configureOptions.Invoke(optionsBuilder); - var client = CreateClient(); + var options = optionsBuilder.Build(); - if (timeout == TimeSpan.Zero) - { - await client.ConnectAsync(options).ConfigureAwait(false); - } - else - { - using (var timeoutToken = new CancellationTokenSource(timeout)) - { - await client.ConnectAsync(options, timeoutToken.Token).ConfigureAwait(false); - } - } + var client = CreateClient(); - return client; + if (timeout == TimeSpan.Zero) + { + await client.ConnectAsync(options).ConfigureAwait(false); } - - public async Task ConnectClient(MqttClientOptionsBuilder options, TimeSpan timeout = default) + else { - ArgumentNullException.ThrowIfNull(options); + using var timeoutToken = new CancellationTokenSource(timeout); + await client.ConnectAsync(options, timeoutToken.Token).ConfigureAwait(false); + } - options = options.WithTcpServer("127.0.0.1", ServerPort); + return client; + } - var client = CreateClient(); + public async Task ConnectClient(MqttClientOptionsBuilder options, TimeSpan timeout = default) + { + ArgumentNullException.ThrowIfNull(options); - if (timeout == TimeSpan.Zero) - { - await client.ConnectAsync(options.Build()).ConfigureAwait(false); - } - else - { - using (var timeoutToken = new CancellationTokenSource(timeout)) - { - await client.ConnectAsync(options.Build(), timeoutToken.Token).ConfigureAwait(false); - } - } + options = options.WithTcpServer("127.0.0.1", ServerPort); - return client; - } + var client = CreateClient(); - public async Task ConnectClient(MqttClientOptions options, TimeSpan timeout = default) + if (timeout == TimeSpan.Zero) { - ArgumentNullException.ThrowIfNull(options); - - var client = CreateClient(); - - if (timeout == TimeSpan.Zero) - { - await client.ConnectAsync(options).ConfigureAwait(false); - } - else - { - using (var timeoutToken = new CancellationTokenSource(timeout)) - { - await client.ConnectAsync(options, timeoutToken.Token).ConfigureAwait(false); - } - } - - return client; + await client.ConnectAsync(options.Build()).ConfigureAwait(false); } - - public async Task ConnectLowLevelClient(Action optionsBuilder = null) + else { - var options = new MqttClientOptionsBuilder(); - options = options.WithTcpServer("127.0.0.1", ServerPort); - optionsBuilder?.Invoke(options); + using var timeoutToken = new CancellationTokenSource(timeout); + await client.ConnectAsync(options.Build(), timeoutToken.Token).ConfigureAwait(false); + } + + return client; + } - var client = CreateLowLevelClient(); - await client.ConnectAsync(options.Build(), CancellationToken.None).ConfigureAwait(false); + public async Task ConnectClient(MqttClientOptions options, TimeSpan timeout = default) + { + ArgumentNullException.ThrowIfNull(options); - return client; - } + var client = CreateClient(); - public async Task ConnectRpcClient(MqttRpcClientOptions options) + if (timeout == TimeSpan.Zero) { - return new MqttRpcClient(await ConnectClient(), options); + await client.ConnectAsync(options).ConfigureAwait(false); } - - public TestApplicationMessageReceivedHandler CreateApplicationMessageHandler(IMqttClient mqttClient) + else { - return new TestApplicationMessageReceivedHandler(mqttClient); + using var timeoutToken = new CancellationTokenSource(timeout); + await client.ConnectAsync(options, timeoutToken.Token).ConfigureAwait(false); } - public IMqttClient CreateClient() - { - var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance; + return client; + } - var client = ClientFactory.CreateMqttClient(logger); + public async Task ConnectLowLevelClient(Action optionsBuilder = null) + { + var options = new MqttClientOptionsBuilder(); + options = options.WithTcpServer("127.0.0.1", ServerPort); + optionsBuilder?.Invoke(options); - client.ConnectingAsync += e => - { - if (TestContext != null) - { - var clientOptions = e.ClientOptions; - var existingClientId = clientOptions.ClientId; - if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName)) - { - clientOptions.ClientId = TestContext.TestName + "_" + existingClientId; - } - } + var client = CreateLowLevelClient(); + await client.ConnectAsync(options.Build(), CancellationToken.None).ConfigureAwait(false); - return CompletedTask.Instance; - }; + return client; + } - lock (_clients) + public async Task ConnectRpcClient(MqttRpcClientOptions options) + { + return new MqttRpcClient(await ConnectClient(), options); + } + + public TestApplicationMessageReceivedHandler CreateApplicationMessageHandler(IMqttClient mqttClient) + { + return new TestApplicationMessageReceivedHandler(mqttClient); + } + + public IMqttClient CreateClient() + { + var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance; + + var client = ClientFactory.CreateMqttClient(logger); + + client.ConnectingAsync += e => + { + if (TestContext != null) { - _clients.Add(client); + var clientOptions = e.ClientOptions; + var existingClientId = clientOptions.ClientId; + if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName)) + { + clientOptions.ClientId = TestContext.TestName + "_" + existingClientId; + } } - return client; - } + return CompletedTask.Instance; + }; - public MqttClientOptions CreateDefaultClientOptions() + lock (_clients) { - return CreateDefaultClientOptionsBuilder().Build(); + _clients.Add(client); } - public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder() - { - return ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(_protocolVersion) - .WithTcpServer("127.0.0.1", ServerPort) - .WithClientId(TestContext.TestName + "_" + Guid.NewGuid()); - } + return client; + } - public ILowLevelMqttClient CreateLowLevelClient() - { - var client = ClientFactory.CreateLowLevelMqttClient(ClientLogger); + public MqttClientOptions CreateDefaultClientOptions() + { + return CreateDefaultClientOptionsBuilder().Build(); + } - lock (_lowLevelClients) - { - _lowLevelClients.Add(client); - } + public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder() + { + return ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(_protocolVersion) + .WithTcpServer("127.0.0.1", ServerPort) + .WithClientId(TestContext.TestName + "_" + Guid.NewGuid()); + } - return client; + public ILowLevelMqttClient CreateLowLevelClient() + { + var client = ClientFactory.CreateLowLevelMqttClient(ClientLogger); + + lock (_lowLevelClients) + { + _lowLevelClients.Add(client); } - public MqttServer CreateServer(MqttServerOptions options) + return client; + } + + public MqttServer CreateServer(MqttServerOptions options) + { + if (Server != null) { - if (Server != null) - { - throw new InvalidOperationException("Server already started."); - } + throw new InvalidOperationException("Server already started."); + } - var logger = EnableLogger ? (IMqttNetLogger)ServerLogger : new MqttNetNullLogger(); + var logger = EnableLogger ? (IMqttNetLogger)ServerLogger : new MqttNetNullLogger(); - Server = ServerFactory.CreateMqttServer(options, logger); + Server = ServerFactory.CreateMqttServer(options, logger); - Server.ValidatingConnectionAsync += e => + Server.ValidatingConnectionAsync += e => + { + if (TestContext != null) { - if (TestContext != null) + // Null is used when the client id is assigned from the server! + if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName)) { - // Null is used when the client id is assigned from the server! - if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName)) - { - TrackException(new InvalidOperationException($"Invalid client ID used ({e.ClientId}). It must start with UnitTest name.")); - e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; - } + TrackException(new InvalidOperationException($"Invalid client ID used ({e.ClientId}). It must start with UnitTest name.")); + e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; } + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - return Server; - } + return Server; + } - public void Dispose() + public void Dispose() + { + try { - try + lock (_clients) { - lock (_clients) + foreach (var mqttClient in _clients) { - foreach (var mqttClient in _clients) + try { - try - { - //mqttClient.DisconnectAsync().GetAwaiter().GetResult(); - } - catch - { - // This can happen when the test already disconnected the client. - } - finally - { - mqttClient?.Dispose(); - } + //mqttClient.DisconnectAsync().GetAwaiter().GetResult(); } - - _clients.Clear(); - } - - lock (_lowLevelClients) - { - foreach (var lowLevelMqttClient in _lowLevelClients) + catch { - lowLevelMqttClient.Dispose(); + // This can happen when the test already disconnected the client. + } + finally + { + mqttClient?.Dispose(); } - - _lowLevelClients.Clear(); } - try - { - Server?.StopAsync().GetAwaiter().GetResult(); - } - catch - { - // This can happen when the test already stopped the server. - } - finally + _clients.Clear(); + } + + lock (_lowLevelClients) + { + foreach (var lowLevelMqttClient in _lowLevelClients) { - Server?.Dispose(); + lowLevelMqttClient.Dispose(); } - Server = null; - - ThrowIfLogErrors(); - - GC.Collect(); - GC.WaitForFullGCComplete(); - GC.WaitForPendingFinalizers(); + _lowLevelClients.Clear(); + } - if (_exceptions.Any()) - { - throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions)); - } + try + { + Server?.StopAsync().GetAwaiter().GetResult(); + } + catch + { + // This can happen when the test already stopped the server. } finally { - TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException; + Server?.Dispose(); } - } - public Task StartServer() + Server = null; + + ThrowIfLogErrors(); + + GC.Collect(); + GC.WaitForFullGCComplete(); + GC.WaitForPendingFinalizers(); + + if (_exceptions.Any()) + { + // ReSharper disable once ThrowExceptionInUnexpectedLocation + throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions)); + } + } + finally { - return StartServer(ServerFactory.CreateServerOptionsBuilder()); + TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException; } + } - public async Task StartServer(MqttServerOptionsBuilder optionsBuilder) - { - optionsBuilder.WithDefaultEndpoint(); - optionsBuilder.WithDefaultEndpointPort(ServerPort); - optionsBuilder.WithMaxPendingMessagesPerClient(int.MaxValue); + public Task StartServer() + { + return StartServer(ServerFactory.CreateServerOptionsBuilder()); + } - var options = optionsBuilder.Build(); - var server = CreateServer(options); - await server.StartAsync().ConfigureAwait(false); + public async Task StartServer(MqttServerOptionsBuilder optionsBuilder) + { + optionsBuilder.WithDefaultEndpoint(); + optionsBuilder.WithDefaultEndpointPort(ServerPort); + optionsBuilder.WithMaxPendingMessagesPerClient(int.MaxValue); - // The OS has chosen the port to we have to properly expose it to the tests. - ServerPort = options.DefaultEndpointOptions.Port; - return server; - } + var options = optionsBuilder.Build(); + var server = CreateServer(options); + await server.StartAsync().ConfigureAwait(false); - public async Task StartServer(Action configure) - { - var optionsBuilder = ServerFactory.CreateServerOptionsBuilder(); + // The OS has chosen the port to we have to properly expose it to the tests. + ServerPort = options.DefaultEndpointOptions.Port; + return server; + } - optionsBuilder.WithDefaultEndpoint(); - optionsBuilder.WithDefaultEndpointPort(ServerPort); - optionsBuilder.WithMaxPendingMessagesPerClient(int.MaxValue); + public async Task StartServer(Action configure) + { + var optionsBuilder = ServerFactory.CreateServerOptionsBuilder(); - configure?.Invoke(optionsBuilder); + optionsBuilder.WithDefaultEndpoint(); + optionsBuilder.WithDefaultEndpointPort(ServerPort); + optionsBuilder.WithMaxPendingMessagesPerClient(int.MaxValue); - var options = optionsBuilder.Build(); - var server = CreateServer(options); - await server.StartAsync().ConfigureAwait(false); + configure?.Invoke(optionsBuilder); - // The OS has chosen the port to we have to properly expose it to the tests. - ServerPort = options.DefaultEndpointOptions.Port; - return server; - } + var options = optionsBuilder.Build(); + var server = CreateServer(options); + await server.StartAsync().ConfigureAwait(false); + + // The OS has chosen the port to we have to properly expose it to the tests. + ServerPort = options.DefaultEndpointOptions.Port; + return server; + } - public void ThrowIfLogErrors() + public void ThrowIfLogErrors() + { + if (!IgnoreServerLogErrors) { - if (!IgnoreServerLogErrors) + lock (_serverErrors) { - lock (_serverErrors) + if (_serverErrors.Count > 0) { - if (_serverErrors.Count > 0) - { - var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."; - Console.WriteLine(message); - throw new Exception(message); - } + var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."; + Console.WriteLine(message); + throw new Exception(message); } } + } - if (!IgnoreClientLogErrors) + if (!IgnoreClientLogErrors) + { + lock (_clientErrors) { - lock (_clientErrors) + if (_clientErrors.Count > 0) { - if (_clientErrors.Count > 0) - { - var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})"; - Console.WriteLine(message); - throw new Exception(message); - } + var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})"; + Console.WriteLine(message); + throw new Exception(message); } } } + } - public void TrackException(Exception exception) + public void TrackException(Exception exception) + { + if (exception == null) { - if (exception == null) - { - return; - } - - lock (_exceptions) - { - _exceptions.Add(exception); - } + return; } - void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + lock (_exceptions) { - TrackException(e.Exception); + _exceptions.Add(exception); } } + + void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + TrackException(e.Exception); + } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Mockups/TestLogger.cs b/Source/MQTTnet.Tests/Mockups/TestLogger.cs index 4cec9d3ca..ab2d535cf 100644 --- a/Source/MQTTnet.Tests/Mockups/TestLogger.cs +++ b/Source/MQTTnet.Tests/Mockups/TestLogger.cs @@ -5,22 +5,21 @@ using System; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Tests.Mockups +namespace MQTTnet.Tests.Mockups; + +public sealed class TestLogger : IMqttNetLogger { - public sealed class TestLogger : IMqttNetLogger - { - public event EventHandler LogMessagePublished; + public event EventHandler LogMessagePublished; - public bool IsEnabled { get; } = true; + public bool IsEnabled { get; } = true; - public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) + public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) + { + LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(new MqttNetLogMessage { - LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(new MqttNetLogMessage - { - Level = logLevel, - Message = string.Format(message, parameters), - Exception = exception - })); - } + Level = logLevel, + Message = string.Format(message, parameters), + Exception = exception + })); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs index 139a30857..817d90df6 100644 --- a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs +++ b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs @@ -8,58 +8,58 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Protocol; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttApplicationMessageBuilder_Tests { - [TestClass] - public sealed class MqttApplicationMessageBuilder_Tests + [TestMethod] + public void CreateApplicationMessage_TopicOnly() { - [TestMethod] - public void CreateApplicationMessage_TopicOnly() - { - var message = new MqttApplicationMessageBuilder().WithTopic("Abc").Build(); - Assert.AreEqual("Abc", message.Topic); - Assert.IsFalse(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - } + var message = new MqttApplicationMessageBuilder().WithTopic("Abc").Build(); + Assert.AreEqual("Abc", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + } - [TestMethod] - public void CreateApplicationMessage_TimeStampPayload() - { - var message = new MqttApplicationMessageBuilder().WithTopic("xyz").WithPayload(TimeSpan.FromSeconds(360).ToString()).Build(); - Assert.AreEqual("xyz", message.Topic); - Assert.IsFalse(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); - } + [TestMethod] + public void CreateApplicationMessage_TimeStampPayload() + { + var message = new MqttApplicationMessageBuilder().WithTopic("xyz").WithPayload(TimeSpan.FromSeconds(360).ToString()).Build(); + Assert.AreEqual("xyz", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); + } - [TestMethod] - public void CreateApplicationMessage_StreamPayload() - { - var stream = new MemoryStream(Encoding.UTF8.GetBytes("xHello")) { Position = 1 }; + [TestMethod] + public void CreateApplicationMessage_StreamPayload() + { + var stream = new MemoryStream("xHello"u8.ToArray()) { Position = 1 }; - var message = new MqttApplicationMessageBuilder().WithTopic("123").WithPayload(stream).Build(); - Assert.AreEqual("123", message.Topic); - Assert.IsFalse(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello"); - } + var message = new MqttApplicationMessageBuilder().WithTopic("123").WithPayload(stream).Build(); + Assert.AreEqual("123", message.Topic); + Assert.IsFalse(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello"); + } - [TestMethod] - public void CreateApplicationMessage_Retained() - { - var message = new MqttApplicationMessageBuilder().WithTopic("lol").WithRetainFlag().Build(); - Assert.AreEqual("lol", message.Topic); - Assert.IsTrue(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - } + [TestMethod] + public void CreateApplicationMessage_Retained() + { + var message = new MqttApplicationMessageBuilder().WithTopic("lol").WithRetainFlag().Build(); + Assert.AreEqual("lol", message.Topic); + Assert.IsTrue(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); + } - [TestMethod] - public void CreateApplicationMessage_QosLevel2() - { - var message = new MqttApplicationMessageBuilder().WithTopic("rofl").WithRetainFlag().WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build(); - Assert.AreEqual("rofl", message.Topic); - Assert.IsTrue(message.Retain); - Assert.AreEqual(MqttQualityOfServiceLevel.ExactlyOnce, message.QualityOfServiceLevel); - } + [TestMethod] + public void CreateApplicationMessage_QosLevel2() + { + var message = new MqttApplicationMessageBuilder().WithTopic("rofl").WithRetainFlag().WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build(); + Assert.AreEqual("rofl", message.Topic); + Assert.IsTrue(message.Retain); + Assert.AreEqual(MqttQualityOfServiceLevel.ExactlyOnce, message.QualityOfServiceLevel); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs index cb47d4994..1674a8ff1 100644 --- a/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs +++ b/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs @@ -6,39 +6,39 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttApplicationMessageValidator_Tests { - [TestClass] - public sealed class MqttApplicationMessageValidator_Tests + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Succeed_When_Using_TopicAlias_And_MQTT_311() { - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void Succeed_When_Using_TopicAlias_And_MQTT_311() - { - MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311); - } + MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311); + } - [TestMethod] - public void Succeed_When_Using_TopicAlias_And_MQTT_500() - { - MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500); - } + [TestMethod] + public void Succeed_When_Using_TopicAlias_And_MQTT_500() + { + MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500); + } - [TestMethod] - public void Succeed_When_Using_UserProperties_And_MQTT_500() - { - MqttApplicationMessageValidator.ThrowIfNotSupported( - new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(), - MqttProtocolVersion.V500); - } + [TestMethod] + public void Succeed_When_Using_UserProperties_And_MQTT_500() + { + MqttApplicationMessageValidator.ThrowIfNotSupported( + new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(), + MqttProtocolVersion.V500); + } - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void Succeed_When_Using_WillUserProperties_And_MQTT_311() - { - MqttApplicationMessageValidator.ThrowIfNotSupported( - new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(), - MqttProtocolVersion.V311); - } + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Succeed_When_Using_WillUserProperties_And_MQTT_311() + { + MqttApplicationMessageValidator.ThrowIfNotSupported( + new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(), + MqttProtocolVersion.V311); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs b/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs index c7b6ab237..e15145a42 100644 --- a/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs +++ b/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs @@ -6,48 +6,48 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttClientOptionsValidator_Tests { - [TestClass] - public sealed class MqttClientOptionsValidator_Tests + [TestMethod] + public void Succeed_When_Using_UserProperties_And_MQTT_500() + { + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build(); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Succeed_When_Using_WillUserProperties_And_MQTT_311() + { + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build(); + } + + [TestMethod] + public void Succeed_When_Using_WillUserProperties_And_MQTT_500() + { + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build(); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Throw_When_Using_UserProperties_And_MQTT_311() + { + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build(); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311() + { + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build(); + } + + [TestMethod] + public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500() { - [TestMethod] - public void Succeed_When_Using_UserProperties_And_MQTT_500() - { - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build(); - } - - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void Succeed_When_Using_WillUserProperties_And_MQTT_311() - { - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build(); - } - - [TestMethod] - public void Succeed_When_Using_WillUserProperties_And_MQTT_500() - { - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build(); - } - - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void Throw_When_Using_UserProperties_And_MQTT_311() - { - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build(); - } - - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311() - { - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build(); - } - - [TestMethod] - public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500() - { - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build(); - } + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttPacketIdentifierProvider_Tests.cs b/Source/MQTTnet.Tests/MqttPacketIdentifierProvider_Tests.cs index 0c69624ca..32c5775d5 100644 --- a/Source/MQTTnet.Tests/MqttPacketIdentifierProvider_Tests.cs +++ b/Source/MQTTnet.Tests/MqttPacketIdentifierProvider_Tests.cs @@ -4,32 +4,32 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public class MqttPacketIdentifierProvider_Tests { - [TestClass] - public class MqttPacketIdentifierProvider_Tests + [TestMethod] + public void Reset() { - [TestMethod] - public void Reset() - { - var p = new MqttPacketIdentifierProvider(); - Assert.AreEqual(1, p.GetNextPacketIdentifier()); - Assert.AreEqual(2, p.GetNextPacketIdentifier()); - p.Reset(); - Assert.AreEqual(1, p.GetNextPacketIdentifier()); - } - - [TestMethod] - public void ReachBoundaries() - { - var p = new MqttPacketIdentifierProvider(); + var p = new MqttPacketIdentifierProvider(); + Assert.AreEqual(1, p.GetNextPacketIdentifier()); + Assert.AreEqual(2, p.GetNextPacketIdentifier()); + p.Reset(); + Assert.AreEqual(1, p.GetNextPacketIdentifier()); + } - for (ushort i = 0; i < ushort.MaxValue; i++) - { - Assert.AreEqual(i + 1, p.GetNextPacketIdentifier()); - } + [TestMethod] + public void ReachBoundaries() + { + var p = new MqttPacketIdentifierProvider(); - Assert.AreEqual(1, p.GetNextPacketIdentifier()); + for (ushort i = 0; i < ushort.MaxValue; i++) + { + Assert.AreEqual(i + 1, p.GetNextPacketIdentifier()); } + + Assert.AreEqual(1, p.GetNextPacketIdentifier()); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs b/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs index 0a3d3f5eb..fc4d84530 100644 --- a/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs +++ b/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs @@ -5,30 +5,30 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public class MqttPacketWriter_Tests { - [TestClass] - public class MqttPacketWriter_Tests + protected virtual MqttBufferWriter WriterFactory() { - protected virtual MqttBufferWriter WriterFactory() - { - return new MqttBufferWriter(4096, 65535); - } + return new MqttBufferWriter(4096, 65535); + } - [TestMethod] - public void WritePacket() - { - var writer = WriterFactory(); - Assert.AreEqual(0, writer.Length); + [TestMethod] + public void WritePacket() + { + var writer = WriterFactory(); + Assert.AreEqual(0, writer.Length); - writer.WriteString("1234567890"); - Assert.AreEqual(10 + 2, writer.Length); + writer.WriteString("1234567890"); + Assert.AreEqual(10 + 2, writer.Length); - writer.WriteBinary(new byte[300]); - Assert.AreEqual(300 + 2 + 12, writer.Length); + writer.WriteBinary(new byte[300]); + Assert.AreEqual(300 + 2 + 12, writer.Length); - writer.WriteBinary(new byte[5000]); - Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length); - } + writer.WriteBinary(new byte[5000]); + Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttTcpChannel_Tests.cs b/Source/MQTTnet.Tests/MqttTcpChannel_Tests.cs index caad82f6c..8af3050ca 100644 --- a/Source/MQTTnet.Tests/MqttTcpChannel_Tests.cs +++ b/Source/MQTTnet.Tests/MqttTcpChannel_Tests.cs @@ -12,6 +12,7 @@ namespace MQTTnet.Tests; +// ReSharper disable InconsistentNaming [TestClass] public class MqttTcpChannel_Tests { diff --git a/Source/MQTTnet.Tests/MqttTopicValidatorSubscribe_Tests.cs b/Source/MQTTnet.Tests/MqttTopicValidatorSubscribe_Tests.cs index 13394a719..8f07d0688 100644 --- a/Source/MQTTnet.Tests/MqttTopicValidatorSubscribe_Tests.cs +++ b/Source/MQTTnet.Tests/MqttTopicValidatorSubscribe_Tests.cs @@ -6,53 +6,53 @@ using MQTTnet.Exceptions; using MQTTnet.Protocol; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public class MqttTopicValidatorSubscribe_Tests { - [TestClass] - public class MqttTopicValidatorSubscribe_Tests + [TestMethod] + public void Valid_Topic() + { + MqttTopicValidator.ThrowIfInvalidSubscribe("/a/b/c"); + } + + [TestMethod] + public void Valid_Topic_Plus_In_Between() + { + MqttTopicValidator.ThrowIfInvalidSubscribe("/a/+/c"); + } + + [TestMethod] + public void Valid_Topic_Plus_Last_Char() + { + MqttTopicValidator.ThrowIfInvalidSubscribe("/a/+"); + } + + [TestMethod] + public void Valid_Topic_Hash_Last_Char() + { + MqttTopicValidator.ThrowIfInvalidSubscribe("/a/#"); + } + + [TestMethod] + public void Valid_Topic_Only_Hash() + { + MqttTopicValidator.ThrowIfInvalidSubscribe("#"); + } + + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void Invalid_Topic_Hash_In_Between() + { + MqttTopicValidator.ThrowIfInvalidSubscribe("/a/#/c"); + } + + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void Invalid_Topic_Empty() { - [TestMethod] - public void Valid_Topic() - { - MqttTopicValidator.ThrowIfInvalidSubscribe("/a/b/c"); - } - - [TestMethod] - public void Valid_Topic_Plus_In_Between() - { - MqttTopicValidator.ThrowIfInvalidSubscribe("/a/+/c"); - } - - [TestMethod] - public void Valid_Topic_Plus_Last_Char() - { - MqttTopicValidator.ThrowIfInvalidSubscribe("/a/+"); - } - - [TestMethod] - public void Valid_Topic_Hash_Last_Char() - { - MqttTopicValidator.ThrowIfInvalidSubscribe("/a/#"); - } - - [TestMethod] - public void Valid_Topic_Only_Hash() - { - MqttTopicValidator.ThrowIfInvalidSubscribe("#"); - } - - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void Invalid_Topic_Hash_In_Between() - { - MqttTopicValidator.ThrowIfInvalidSubscribe("/a/#/c"); - } - - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void Invalid_Topic_Empty() - { - MqttTopicValidator.ThrowIfInvalidSubscribe(string.Empty); - } + MqttTopicValidator.ThrowIfInvalidSubscribe(string.Empty); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs b/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs index bd22742d8..26fa2323f 100644 --- a/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs +++ b/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs @@ -6,36 +6,36 @@ using MQTTnet.Exceptions; using MQTTnet.Protocol; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttTopicValidator_Tests { - [TestClass] - public sealed class MqttTopicValidator_Tests + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void Invalid_Topic_Empty() { - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void Invalid_Topic_Empty() - { - MqttTopicValidator.ThrowIfInvalid(string.Empty); - } + MqttTopicValidator.ThrowIfInvalid(string.Empty); + } - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void Invalid_Topic_Hash() - { - MqttTopicValidator.ThrowIfInvalid("/a/#/c"); - } + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void Invalid_Topic_Hash() + { + MqttTopicValidator.ThrowIfInvalid("/a/#/c"); + } - [TestMethod] - [ExpectedException(typeof(MqttProtocolViolationException))] - public void Invalid_Topic_Plus() - { - MqttTopicValidator.ThrowIfInvalid("/a/+/c"); - } + [TestMethod] + [ExpectedException(typeof(MqttProtocolViolationException))] + public void Invalid_Topic_Plus() + { + MqttTopicValidator.ThrowIfInvalid("/a/+/c"); + } - [TestMethod] - public void Valid_Topic() - { - MqttTopicValidator.ThrowIfInvalid("/a/b/c"); - } + [TestMethod] + public void Valid_Topic() + { + MqttTopicValidator.ThrowIfInvalid("/a/b/c"); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Protocol_Tests.cs b/Source/MQTTnet.Tests/Protocol_Tests.cs index 6f5146c8d..30a57986c 100644 --- a/Source/MQTTnet.Tests/Protocol_Tests.cs +++ b/Source/MQTTnet.Tests/Protocol_Tests.cs @@ -5,51 +5,51 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Protocol_Tests { - [TestClass] - public sealed class Protocol_Tests + [TestMethod] + public void Encode_Four_Byte_Integer() { - [TestMethod] - public void Encode_Four_Byte_Integer() - { - var writer = new MqttBufferWriter(4, 4); + var writer = new MqttBufferWriter(4, 4); - for (uint value = 0; value < 268435455; value++) - { - writer.WriteVariableByteInteger(value); + for (uint value = 0; value < 268435455; value++) + { + writer.WriteVariableByteInteger(value); - var buffer = writer.GetBuffer(); + var buffer = writer.GetBuffer(); - var reader = new MqttBufferReader(); - reader.SetBuffer(buffer, 0, writer.Length); - var checkValue = reader.ReadVariableByteInteger(); + var reader = new MqttBufferReader(); + reader.SetBuffer(buffer, 0, writer.Length); + var checkValue = reader.ReadVariableByteInteger(); - Assert.AreEqual(value, checkValue); + Assert.AreEqual(value, checkValue); - writer.Reset(0); - } + writer.Reset(0); } + } - [TestMethod] - public void Encode_Two_Byte_Integer() - { - var writer = new MqttBufferWriter(2, 2); + [TestMethod] + public void Encode_Two_Byte_Integer() + { + var writer = new MqttBufferWriter(2, 2); - for (ushort value = 0; value < ushort.MaxValue; value++) - { - writer.WriteTwoByteInteger(value); + for (ushort value = 0; value < ushort.MaxValue; value++) + { + writer.WriteTwoByteInteger(value); - var buffer = writer.GetBuffer(); + var buffer = writer.GetBuffer(); - var reader = new MqttBufferReader(); - reader.SetBuffer(buffer, 0, writer.Length); - var checkValue = reader.ReadTwoByteInteger(); + var reader = new MqttBufferReader(); + reader.SetBuffer(buffer, 0, writer.Length); + var checkValue = reader.ReadTwoByteInteger(); - Assert.AreEqual(value, checkValue); + Assert.AreEqual(value, checkValue); - writer.Reset(0); - } + writer.Reset(0); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/RoundtripTime_Tests.cs b/Source/MQTTnet.Tests/RoundtripTime_Tests.cs index b1359bc3d..d3d99f0e8 100644 --- a/Source/MQTTnet.Tests/RoundtripTime_Tests.cs +++ b/Source/MQTTnet.Tests/RoundtripTime_Tests.cs @@ -10,52 +10,50 @@ using MQTTnet.Internal; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests -{ - [TestClass] - public class RoundtripTime_Tests - { - public TestContext TestContext { get; set; } +namespace MQTTnet.Tests; - [TestMethod] - public async Task Round_Trip_Time() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); +// ReSharper disable InconsistentNaming +[TestClass] +public class RoundtripTime_Tests +{ + public TestContext TestContext { get; set; } - var receiverClient = await testEnvironment.ConnectClient(); - var senderClient = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task Round_Trip_Time() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - TaskCompletionSource response = null; + var receiverClient = await testEnvironment.ConnectClient(); + var senderClient = await testEnvironment.ConnectClient(); - receiverClient.ApplicationMessageReceivedAsync += e => - { - response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString()); - return CompletedTask.Instance; - }; + TaskCompletionSource response = null; - await receiverClient.SubscribeAsync("#"); + receiverClient.ApplicationMessageReceivedAsync += e => + { + response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString()); + return CompletedTask.Instance; + }; - var times = new List(); - var stopwatch = Stopwatch.StartNew(); + await receiverClient.SubscribeAsync("#"); - await Task.Delay(1000); + var times = new List(); + var stopwatch = Stopwatch.StartNew(); - for (var i = 0; i < 100; i++) - { - response = new TaskCompletionSource(); - await senderClient.PublishStringAsync("test", DateTime.UtcNow.Ticks.ToString()); - if (!response.Task.Wait(TimeSpan.FromSeconds(5))) - { - throw new TimeoutException(); - } + await Task.Delay(1000); - stopwatch.Stop(); - times.Add(stopwatch.Elapsed); - stopwatch.Restart(); - } + for (var i = 0; i < 100; i++) + { + response = new TaskCompletionSource(); + await senderClient.PublishStringAsync("test", DateTime.UtcNow.Ticks.ToString()); + if (!response.Task.Wait(TimeSpan.FromSeconds(5))) + { + throw new TimeoutException(); } + + stopwatch.Stop(); + times.Add(stopwatch.Elapsed); + stopwatch.Restart(); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Assigned_Client_ID_Tests.cs b/Source/MQTTnet.Tests/Server/Assigned_Client_ID_Tests.cs index 69ed38875..798a16fa2 100644 --- a/Source/MQTTnet.Tests/Server/Assigned_Client_ID_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Assigned_Client_ID_Tests.cs @@ -9,84 +9,82 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Assigned_Client_ID_Tests : BaseTestClass { - [TestClass] - public sealed class Assigned_Client_ID_Tests : BaseTestClass + [TestMethod] + public Task Connect_With_No_Client_Id() { - [TestMethod] - public Task Connect_With_No_Client_Id() - { - return Connect_With_Client_Id("test_123", "test_123", null, "test_123"); - } + return Connect_With_Client_Id("test_123", "test_123", null, "test_123"); + } - [TestMethod] - public Task Connect_With_Client_Id() - { - return Connect_With_Client_Id("Connect_With_Client_Id_test_456", null, "test_456", null); - } + [TestMethod] + public Task Connect_With_Client_Id() + { + return Connect_With_Client_Id("Connect_With_Client_Id_test_456", null, "test_456", null); + } - async Task Connect_With_Client_Id(string expectedClientId, string expectedReturnedClientId, string usedClientId, string assignedClientId) - { - using (var testEnvironment = CreateTestEnvironment()) - { - string serverConnectedClientId = null; - string serverDisconnectedClientId = null; - string clientAssignedClientId = null; + async Task Connect_With_Client_Id(string expectedClientId, string expectedReturnedClientId, string usedClientId, string assignedClientId) + { + using var testEnvironment = CreateTestEnvironment(); + string serverConnectedClientId = null; + string serverDisconnectedClientId = null; + string clientAssignedClientId = null; - // Arrange server - var disconnectedMre = new ManualResetEventSlim(); + // Arrange server + var disconnectedMre = new ManualResetEventSlim(); - var server = await testEnvironment.StartServer(); - server.ValidatingConnectionAsync += e => - { - if (string.IsNullOrEmpty(e.ClientId)) - { - e.AssignedClientIdentifier = assignedClientId; - e.ReasonCode = MqttConnectReasonCode.Success; - } + var server = await testEnvironment.StartServer(); + server.ValidatingConnectionAsync += e => + { + if (string.IsNullOrEmpty(e.ClientId)) + { + e.AssignedClientIdentifier = assignedClientId; + e.ReasonCode = MqttConnectReasonCode.Success; + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - testEnvironment.Server.ClientConnectedAsync += args => - { - serverConnectedClientId = args.ClientId; - return CompletedTask.Instance; - }; + testEnvironment.Server.ClientConnectedAsync += args => + { + serverConnectedClientId = args.ClientId; + return CompletedTask.Instance; + }; - testEnvironment.Server.ClientDisconnectedAsync += args => - { - serverDisconnectedClientId = args.ClientId; - disconnectedMre.Set(); - return CompletedTask.Instance; - }; + testEnvironment.Server.ClientDisconnectedAsync += args => + { + serverDisconnectedClientId = args.ClientId; + disconnectedMre.Set(); + return CompletedTask.Instance; + }; - // Arrange client - var client = testEnvironment.CreateClient(); - client.ConnectedAsync += args => - { - clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier; - return CompletedTask.Instance; - }; + // Arrange client + var client = testEnvironment.CreateClient(); + client.ConnectedAsync += args => + { + clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier; + return CompletedTask.Instance; + }; - // Act - await client.ConnectAsync(new MqttClientOptionsBuilder() - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithClientId(usedClientId) - .Build()); + // Act + await client.ConnectAsync(new MqttClientOptionsBuilder() + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithClientId(usedClientId) + .Build()); - await client.DisconnectAsync(); + await client.DisconnectAsync(); - // Wait for ClientDisconnectedHandler to trigger - disconnectedMre.Wait(1000); + // Wait for ClientDisconnectedHandler to trigger + disconnectedMre.Wait(1000); - // Assert - Assert.AreEqual(expectedClientId, serverConnectedClientId); - Assert.AreEqual(expectedClientId, serverDisconnectedClientId); - Assert.AreEqual(expectedReturnedClientId, clientAssignedClientId); - } - } + // Assert + Assert.AreEqual(expectedClientId, serverConnectedClientId); + Assert.AreEqual(expectedClientId, serverDisconnectedClientId); + Assert.AreEqual(expectedReturnedClientId, clientAssignedClientId); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Connection_Tests.cs b/Source/MQTTnet.Tests/Server/Connection_Tests.cs index ef2a482c4..3b44df660 100644 --- a/Source/MQTTnet.Tests/Server/Connection_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Connection_Tests.cs @@ -5,78 +5,73 @@ using System; using System.Net; using System.Net.Sockets; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Implementations; using MQTTnet.Server; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Connection_Tests : BaseTestClass { - [TestClass] - public sealed class Connection_Tests : BaseTestClass + [TestMethod] + public async Task Close_Idle_Connection_On_Connect() { - [TestMethod] - public async Task Close_Idle_Connection_On_Connect() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); - var client = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp); - await client.ConnectAsync(new DnsEndPoint("localhost", testEnvironment.ServerPort), CancellationToken.None); + var client = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp); + await client.ConnectAsync(new DnsEndPoint("localhost", testEnvironment.ServerPort), CancellationToken.None); - // Don't send anything. The server should close the connection. - await Task.Delay(TimeSpan.FromSeconds(3)); + // 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); - if (receivedBytes == 0) - { - return; - } - - Assert.Fail("Receive should throw an exception."); - } - catch (SocketException) - { - } + try + { + var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + if (receivedBytes == 0) + { + return; } - } - [TestMethod] - public async Task Send_Garbage() + Assert.Fail("Receive should throw an exception."); + } + catch (SocketException) { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); + } + } - // 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 CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp); - await client.ConnectAsync(new DnsEndPoint("localhost", testEnvironment.ServerPort), CancellationToken.None); + [TestMethod] + public async Task Send_Garbage() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); - var buffer = Encoding.UTF8.GetBytes("Garbage"); - await client.SendAsync(new ArraySegment(buffer), SocketFlags.None); + // 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 CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp); + await client.ConnectAsync(new DnsEndPoint("localhost", testEnvironment.ServerPort), CancellationToken.None); - await Task.Delay(TimeSpan.FromSeconds(3)); + var buffer = "Garbage"u8.ToArray(); + await client.SendAsync(new ArraySegment(buffer), SocketFlags.None); - try - { - var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); - if (receivedBytes == 0) - { - return; - } + await Task.Delay(TimeSpan.FromSeconds(3)); - Assert.Fail("Receive should throw an exception."); - } - catch (SocketException) - { - } + try + { + var receivedBytes = await client.ReceiveAsync(new ArraySegment(new byte[10]), SocketFlags.Partial); + if (receivedBytes == 0) + { + return; } + + Assert.Fail("Receive should throw an exception."); + } + catch (SocketException) + { } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs b/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs index 83a30ecfe..585730d79 100644 --- a/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs @@ -4,63 +4,59 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Cross_Version_Tests : BaseTestClass { - [TestClass] - public sealed class Cross_Version_Tests : BaseTestClass + [TestMethod] + public async Task Send_V311_Receive_V500() { - [TestMethod] - public async Task Send_V311_Receive_V500() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var receiver = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); - await receiver.SubscribeAsync("#"); + var receiver = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + await receiver.SubscribeAsync("#"); - var sender = await testEnvironment.ConnectClient(); + var sender = await testEnvironment.ConnectClient(); - var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("My/Message").WithPayload("My_Payload").Build(); - await sender.PublishAsync(applicationMessage); + var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("My/Message").WithPayload("My_Payload").Build(); + await sender.PublishAsync(applicationMessage); - await LongTestDelay(); + await LongTestDelay(); - Assert.AreEqual(1, receivedApplicationMessages.ReceivedEventArgs.Count); - Assert.AreEqual("My/Message", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.Topic); - Assert.AreEqual("My_Payload", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.ConvertPayloadToString()); - } - } + Assert.AreEqual(1, receivedApplicationMessages.ReceivedEventArgs.Count); + Assert.AreEqual("My/Message", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.Topic); + Assert.AreEqual("My_Payload", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.ConvertPayloadToString()); + } - [TestMethod] - public async Task Send_V500_Receive_V311() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Send_V500_Receive_V311() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var receiver = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V311)); - var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); - await receiver.SubscribeAsync("#"); + var receiver = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V311)); + var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + await receiver.SubscribeAsync("#"); - var sender = await testEnvironment.ConnectClient(); + var sender = await testEnvironment.ConnectClient(); - var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("My/Message") - .WithPayload("My_Payload") - .WithUserProperty("A", "B") - .WithResponseTopic("Response") - .WithCorrelationData(Encoding.UTF8.GetBytes("Correlation")) - .Build(); + var applicationMessage = new MqttApplicationMessageBuilder().WithTopic("My/Message") + .WithPayload("My_Payload") + .WithUserProperty("A", "B") + .WithResponseTopic("Response") + .WithCorrelationData(Encoding.UTF8.GetBytes("Correlation")) + .Build(); - await sender.PublishAsync(applicationMessage); + await sender.PublishAsync(applicationMessage); - await LongTestDelay(); + await LongTestDelay(); - Assert.AreEqual(1, receivedApplicationMessages.ReceivedEventArgs.Count); - Assert.AreEqual("My/Message", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.Topic); - Assert.AreEqual("My_Payload", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.ConvertPayloadToString()); - } - } + Assert.AreEqual(1, receivedApplicationMessages.ReceivedEventArgs.Count); + Assert.AreEqual("My/Message", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.Topic); + Assert.AreEqual("My_Payload", receivedApplicationMessages.ReceivedEventArgs.First().ApplicationMessage.ConvertPayloadToString()); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Events_Tests.cs b/Source/MQTTnet.Tests/Server/Events_Tests.cs index bce9197ed..183acec43 100644 --- a/Source/MQTTnet.Tests/Server/Events_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Events_Tests.cs @@ -10,194 +10,180 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Events_Tests : BaseTestClass { - [TestClass] - public sealed class Events_Tests : BaseTestClass + [TestMethod] + public async Task Fire_Client_Connected_Event() { - [TestMethod] - public async Task Fire_Client_Connected_Event() + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); + + ClientConnectedEventArgs eventArgs = null; + server.ClientConnectedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + eventArgs = e; + return CompletedTask.Instance; + }; - ClientConnectedEventArgs eventArgs = null; - server.ClientConnectedAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser", "ThePassword")); - await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser", "ThePassword")); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs); + Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event))); + Assert.IsTrue(eventArgs.RemoteEndPoint.ToString().Contains("127.0.0.1")); + Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion); + Assert.AreEqual("TheUser", eventArgs.UserName); + Assert.AreEqual("ThePassword", eventArgs.Password); + } - Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event))); - Assert.IsTrue(eventArgs.RemoteEndPoint.ToString().Contains("127.0.0.1")); - Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion); - Assert.AreEqual("TheUser", eventArgs.UserName); - Assert.AreEqual("ThePassword", eventArgs.Password); - } - } + [TestMethod] + public async Task Fire_Client_Disconnected_Event() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Fire_Client_Disconnected_Event() + ClientDisconnectedEventArgs eventArgs = null; + server.ClientDisconnectedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + eventArgs = e; + return CompletedTask.Instance; + }; - ClientDisconnectedEventArgs eventArgs = null; - server.ClientDisconnectedAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser", "ThePassword")); + await client.DisconnectAsync(); - var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser", "ThePassword")); - await client.DisconnectAsync(); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs); + Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event))); + Assert.IsTrue(eventArgs.RemoteEndPoint.ToString().Contains("127.0.0.1")); + Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType); - Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event))); - Assert.IsTrue(eventArgs.RemoteEndPoint.ToString().Contains("127.0.0.1")); - Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType); + Assert.AreEqual("TheUser", eventArgs.UserName); + Assert.AreEqual("ThePassword", eventArgs.Password); + } - Assert.AreEqual("TheUser", eventArgs.UserName); - Assert.AreEqual("ThePassword", eventArgs.Password); - } - } + [TestMethod] + public async Task Fire_Client_Subscribed_Event() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Fire_Client_Subscribed_Event() + ClientSubscribedTopicEventArgs eventArgs = null; + server.ClientSubscribedTopicAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + eventArgs = e; + return CompletedTask.Instance; + }; - ClientSubscribedTopicEventArgs eventArgs = null; - server.ClientSubscribedTopicAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); + await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce); - var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); - await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs); + Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Subscribed_Event))); + Assert.AreEqual("The/Topic", eventArgs.TopicFilter.Topic); + Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, eventArgs.TopicFilter.QualityOfServiceLevel); + Assert.AreEqual("TheUser", eventArgs.UserName); + } - Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Subscribed_Event))); - Assert.AreEqual("The/Topic", eventArgs.TopicFilter.Topic); - Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, eventArgs.TopicFilter.QualityOfServiceLevel); - Assert.AreEqual("TheUser", eventArgs.UserName); - } - } + [TestMethod] + public async Task Fire_Client_Unsubscribed_Event() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Fire_Client_Unsubscribed_Event() + ClientUnsubscribedTopicEventArgs eventArgs = null; + server.ClientUnsubscribedTopicAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + eventArgs = e; + return CompletedTask.Instance; + }; - ClientUnsubscribedTopicEventArgs eventArgs = null; - server.ClientUnsubscribedTopicAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); + await client.UnsubscribeAsync("The/Topic"); - var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); - await client.UnsubscribeAsync("The/Topic"); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs); + Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Unsubscribed_Event))); + Assert.AreEqual("The/Topic", eventArgs.TopicFilter); + Assert.AreEqual("TheUser", eventArgs.UserName); + } - Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Unsubscribed_Event))); - Assert.AreEqual("The/Topic", eventArgs.TopicFilter); - Assert.AreEqual("TheUser", eventArgs.UserName); - } - } + [TestMethod] + public async Task Fire_Application_Message_Received_Event() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Fire_Application_Message_Received_Event() + InterceptingPublishEventArgs eventArgs = null; + server.InterceptingPublishAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + eventArgs = e; + return CompletedTask.Instance; + }; - InterceptingPublishEventArgs eventArgs = null; - server.InterceptingPublishAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); + await client.PublishStringAsync("The_Topic", "The_Payload"); - var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); - await client.PublishStringAsync("The_Topic", "The_Payload"); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs); + Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Application_Message_Received_Event))); + Assert.AreEqual("The_Topic", eventArgs.ApplicationMessage.Topic); + Assert.AreEqual("The_Payload", eventArgs.ApplicationMessage.ConvertPayloadToString()); + Assert.AreEqual("TheUser", eventArgs.UserName); + } - Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Application_Message_Received_Event))); - Assert.AreEqual("The_Topic", eventArgs.ApplicationMessage.Topic); - Assert.AreEqual("The_Payload", eventArgs.ApplicationMessage.ConvertPayloadToString()); - Assert.AreEqual("TheUser", eventArgs.UserName); - } - } + [TestMethod] + public async Task Fire_Started_Event() + { + using var testEnvironment = CreateTestEnvironment(); + var server = testEnvironment.CreateServer(new MqttServerOptions()); - [TestMethod] - public async Task Fire_Started_Event() + EventArgs eventArgs = null; + server.StartedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = testEnvironment.CreateServer(new MqttServerOptions()); + eventArgs = e; + return CompletedTask.Instance; + }; - EventArgs eventArgs = null; - server.StartedAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + await server.StartAsync(); - await server.StartAsync(); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); + } - Assert.IsNotNull(eventArgs); - } - } + [TestMethod] + public async Task Fire_Stopped_Event() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Fire_Stopped_Event() + EventArgs eventArgs = null; + server.StoppedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); - - EventArgs eventArgs = null; - server.StoppedAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + eventArgs = e; + return CompletedTask.Instance; + }; - await server.StopAsync(); + await server.StopAsync(); - await LongTestDelay(); + await LongTestDelay(); - Assert.IsNotNull(eventArgs); - } - } + Assert.IsNotNull(eventArgs); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 45cff1983..30b8c7b62 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -15,1018 +15,965 @@ using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class General_Tests : BaseTestClass { - [TestClass] - public sealed class General_Tests : BaseTestClass + Dictionary _connected; + + [TestMethod] + public async Task Client_Disconnect_Without_Errors() { - Dictionary _connected; + using var testEnvironment = CreateTestEnvironment(); + bool clientWasConnected; - [TestMethod] - public async Task Client_Disconnect_Without_Errors() + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); + try { - using (var testEnvironment = CreateTestEnvironment()) - { - bool clientWasConnected; + var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); - try - { - var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); + clientWasConnected = true; - clientWasConnected = true; + await client.DisconnectAsync(); - await client.DisconnectAsync(); + await Task.Delay(500); + } + finally + { + await server.StopAsync(); + } - await Task.Delay(500); - } - finally - { - await server.StopAsync(); - } + Assert.IsTrue(clientWasConnected); - Assert.IsTrue(clientWasConnected); + testEnvironment.ThrowIfLogErrors(); + } - testEnvironment.ThrowIfLogErrors(); - } - } + [TestMethod] + public async Task Collect_Messages_In_Disconnected_Session() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - [TestMethod] - public async Task Collect_Messages_In_Disconnected_Session() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + // Create the session including the subscription. + var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false).WithSessionExpiryInterval(60)); + await client1.SubscribeAsync("x"); + await client1.DisconnectAsync(); + await Task.Delay(500); - // Create the session including the subscription. - var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false).WithSessionExpiryInterval(60)); - await client1.SubscribeAsync("x"); - await client1.DisconnectAsync(); - await Task.Delay(500); + var clientStatus = await server.GetClientsAsync(); + Assert.AreEqual(0, clientStatus.Count); - var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(0, clientStatus.Count); + var client2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("b").WithCleanSession(false).WithSessionExpiryInterval(60)); + await client2.PublishStringAsync("x", "1"); + await client2.PublishStringAsync("x", "2"); + await client2.PublishStringAsync("x", "3"); + await client2.DisconnectAsync(); - var client2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("b").WithCleanSession(false).WithSessionExpiryInterval(60)); - await client2.PublishStringAsync("x", "1"); - await client2.PublishStringAsync("x", "2"); - await client2.PublishStringAsync("x", "3"); - await client2.DisconnectAsync(); + await Task.Delay(500); - await Task.Delay(500); + clientStatus = await server.GetClientsAsync(); + var sessionStatus = await server.GetSessionsAsync(); - clientStatus = await server.GetClientsAsync(); - var sessionStatus = await server.GetSessionsAsync(); + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); - Assert.AreEqual(0, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); + Assert.AreEqual(3, sessionStatus.First(s => s.Id == client1.Options.ClientId).PendingApplicationMessagesCount); + } - Assert.AreEqual(3, sessionStatus.First(s => s.Id == client1.Options.ClientId).PendingApplicationMessagesCount); - } - } + [TestMethod] + public async Task Deny_Connection() + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; + + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Deny_Connection() + server.ValidatingConnectionAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; + e.ReasonCode = MqttConnectReasonCode.NotAuthorized; + return CompletedTask.Instance; + }; - var server = await testEnvironment.StartServer(); + var client = testEnvironment.CreateClient(); + var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptions()); - server.ValidatingConnectionAsync += e => - { - e.ReasonCode = MqttConnectReasonCode.NotAuthorized; - return CompletedTask.Instance; - }; - - var client = testEnvironment.CreateClient(); - var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptions()); + Assert.AreEqual(MqttClientConnectResultCode.NotAuthorized, response.ResultCode); + } - Assert.AreEqual(MqttClientConnectResultCode.NotAuthorized, response.ResultCode); - } - } + [TestMethod] + public async Task Do_Not_Send_Retained_Messages_For_Denied_Subscription() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Do_Not_Send_Retained_Messages_For_Denied_Subscription() + server.InterceptingSubscriptionAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) + // This should lead to no subscriptions for "n" at all. So also no sending of retained messages. + if (e.TopicFilter.Topic == "n") { - var server = await testEnvironment.StartServer(); + e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError; + } - server.InterceptingSubscriptionAsync += e => - { - // This should lead to no subscriptions for "n" at all. So also no sending of retained messages. - if (e.TopicFilter.Topic == "n") - { - e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError; - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + // Prepare some retained messages. + var client1 = await testEnvironment.ConnectClient(); + await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("y").WithPayload("x").WithRetainFlag().Build()); + await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("n").WithPayload("x").WithRetainFlag().Build()); + await client1.DisconnectAsync(); - // Prepare some retained messages. - var client1 = await testEnvironment.ConnectClient(); - await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("y").WithPayload("x").WithRetainFlag().Build()); - await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("n").WithPayload("x").WithRetainFlag().Build()); - await client1.DisconnectAsync(); + await Task.Delay(500); - await Task.Delay(500); + // Subscribe to all retained message types. + // It is important to do this in a range of filters to ensure that a subscription is not "hidden". + var client2 = await testEnvironment.ConnectClient(); - // Subscribe to all retained message types. - // It is important to do this in a range of filters to ensure that a subscription is not "hidden". - var client2 = await testEnvironment.ConnectClient(); + var buffer = new StringBuilder(); - var buffer = new StringBuilder(); + client2.ApplicationMessageReceivedAsync += e => + { + lock (buffer) + { + buffer.Append(e.ApplicationMessage.Topic); + } - client2.ApplicationMessageReceivedAsync += e => - { - lock (buffer) - { - buffer.Append(e.ApplicationMessage.Topic); - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + await client2.SubscribeAsync("y"); + await client2.SubscribeAsync("n"); - await client2.SubscribeAsync("y"); - await client2.SubscribeAsync("n"); + await Task.Delay(500); - await Task.Delay(500); + // No 'n' in buffer! + Assert.AreEqual("y", buffer.ToString()); + } - // No 'n' in buffer! - Assert.AreEqual("y", buffer.ToString()); - } - } + [TestMethod] + public async Task Handle_Clean_Disconnect() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); + + var clientConnectedCalled = 0; + var clientDisconnectedCalled = 0; - [TestMethod] - public async Task Handle_Clean_Disconnect() + server.ClientConnectedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); + Interlocked.Increment(ref clientConnectedCalled); + return CompletedTask.Instance; + }; - var clientConnectedCalled = 0; - var clientDisconnectedCalled = 0; + server.ClientDisconnectedAsync += _ => + { + Interlocked.Increment(ref clientDisconnectedCalled); + return CompletedTask.Instance; + }; - server.ClientConnectedAsync += e => - { - Interlocked.Increment(ref clientConnectedCalled); - return CompletedTask.Instance; - }; + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); - server.ClientDisconnectedAsync += e => - { - Interlocked.Increment(ref clientDisconnectedCalled); - return CompletedTask.Instance; - }; + await Task.Delay(1000); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); + Assert.AreEqual(1, clientConnectedCalled); + Assert.AreEqual(0, clientDisconnectedCalled); - await Task.Delay(1000); + await Task.Delay(1000); - Assert.AreEqual(1, clientConnectedCalled); - Assert.AreEqual(0, clientDisconnectedCalled); + await c1.DisconnectAsync(); - await Task.Delay(1000); + await Task.Delay(1000); - await c1.DisconnectAsync(); + Assert.AreEqual(1, clientConnectedCalled); + Assert.AreEqual(1, clientDisconnectedCalled); + } - await Task.Delay(1000); + [TestMethod] + public async Task Handle_Lots_Of_Parallel_Retained_Messages() + { + const int clientCount = 50; - Assert.AreEqual(1, clientConnectedCalled); - Assert.AreEqual(1, clientDisconnectedCalled); - } - } + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Handle_Lots_Of_Parallel_Retained_Messages() + var tasks = new List(); + for (var i = 0; i < clientCount; i++) { - const int clientCount = 50; + var i2 = i; + var testEnvironment2 = testEnvironment; - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + tasks.Add( + Task.Run( + async () => + { + try + { + using var client = await testEnvironment2.ConnectClient(); + // Clear retained message. + await client.PublishAsync( + new MqttApplicationMessageBuilder().WithTopic("r" + i2) + .WithPayload(EmptyBuffer.Array) + .WithRetainFlag() + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .Build()); + + // Set retained message. + await client.PublishAsync( + new MqttApplicationMessageBuilder().WithTopic("r" + i2) + .WithPayload("value") + .WithRetainFlag() + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .Build()); + + await client.DisconnectAsync(); + } + catch (Exception exception) + { + testEnvironment2.TrackException(exception); + } + })); - var tasks = new List(); - for (var i = 0; i < clientCount; i++) - { - var i2 = i; - var testEnvironment2 = testEnvironment; - - tasks.Add( - Task.Run( - async () => - { - try - { - using (var client = await testEnvironment2.ConnectClient()) - { - // Clear retained message. - await client.PublishAsync( - new MqttApplicationMessageBuilder().WithTopic("r" + i2) - .WithPayload(EmptyBuffer.Array) - .WithRetainFlag() - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .Build()); - - // Set retained message. - await client.PublishAsync( - new MqttApplicationMessageBuilder().WithTopic("r" + i2) - .WithPayload("value") - .WithRetainFlag() - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .Build()); - - await client.DisconnectAsync(); - } - } - catch (Exception exception) - { - testEnvironment2.TrackException(exception); - } - })); - - await Task.Delay(10); - } + await Task.Delay(10); + } - await Task.WhenAll(tasks); + await Task.WhenAll(tasks); - await Task.Delay(1000); + await Task.Delay(1000); - var retainedMessages = await server.GetRetainedMessagesAsync(); + var retainedMessages = await server.GetRetainedMessagesAsync(); - Assert.AreEqual(clientCount, retainedMessages.Count); + Assert.AreEqual(clientCount, retainedMessages.Count); - for (var i = 0; i < clientCount; i++) - { - Assert.IsTrue(retainedMessages.Any(m => m.Topic == "r" + i)); - } - } + for (var i = 0; i < clientCount; i++) + { + Assert.IsTrue(retainedMessages.Any(m => m.Topic == "r" + i)); } + } - [TestMethod] - public async Task Intercept_Application_Message() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + [TestMethod] + public async Task Intercept_Application_Message() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - server.InterceptingPublishAsync += e => - { - e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" }; + server.InterceptingPublishAsync += e => + { + e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" }; - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - string receivedTopic = null; - var c1 = await testEnvironment.ConnectClient(); - await c1.SubscribeAsync("#"); + string receivedTopic = null; + var c1 = await testEnvironment.ConnectClient(); + await c1.SubscribeAsync("#"); - c1.ApplicationMessageReceivedAsync += a => - { - receivedTopic = a.ApplicationMessage.Topic; - return CompletedTask.Instance; - }; + c1.ApplicationMessageReceivedAsync += a => + { + receivedTopic = a.ApplicationMessage.Topic; + return CompletedTask.Instance; + }; - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build()); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build()); - await Task.Delay(500); - Assert.AreEqual("new_topic", receivedTopic); - } - } + await Task.Delay(500); + Assert.AreEqual("new_topic", receivedTopic); + } - [TestMethod] - public async Task Intercept_Message() + [TestMethod] + public async Task Intercept_Message() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); + server.InterceptingPublishAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); - server.InterceptingPublishAsync += e => - { - e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("extended")); - return CompletedTask.Instance; - }; + e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("extended")); + return CompletedTask.Instance; + }; - var c1 = await testEnvironment.ConnectClient(); - var c2 = await testEnvironment.ConnectClient(); - await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("test").Build()); + var c1 = await testEnvironment.ConnectClient(); + var c2 = await testEnvironment.ConnectClient(); + await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("test").Build()); - var isIntercepted = false; - c2.ApplicationMessageReceivedAsync += e => - { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; - return CompletedTask.Instance; - }; + var isIntercepted = false; + c2.ApplicationMessageReceivedAsync += e => + { + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; + return CompletedTask.Instance; + }; - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build()); - await c1.DisconnectAsync(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build()); + await c1.DisconnectAsync(); - await Task.Delay(500); + await Task.Delay(500); - Assert.IsTrue(isIntercepted); - } - } + Assert.IsTrue(isIntercepted); + } - [TestMethod] - public async Task Intercept_Undelivered() + [TestMethod] + public async Task Intercept_Undelivered() + { + using var testEnvironment = CreateTestEnvironment(); + var undeliverd = string.Empty; + + var server = await testEnvironment.StartServer(); + server.ApplicationMessageNotConsumedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var undeliverd = string.Empty; + undeliverd = e.ApplicationMessage.Topic; + return CompletedTask.Instance; + }; - var server = await testEnvironment.StartServer(); - server.ApplicationMessageNotConsumedAsync += e => - { - undeliverd = e.ApplicationMessage.Topic; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); - var client = await testEnvironment.ConnectClient(); + await client.SubscribeAsync("b"); - await client.SubscribeAsync("b"); + await client.PublishStringAsync("a", null, MqttQualityOfServiceLevel.ExactlyOnce); - await client.PublishStringAsync("a", null, MqttQualityOfServiceLevel.ExactlyOnce); + await Task.Delay(500); - await Task.Delay(500); + Assert.AreEqual("a", undeliverd); + } - Assert.AreEqual("a", undeliverd); - } - } + [TestMethod] + public async Task No_Messages_If_No_Subscription() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - [TestMethod] - public async Task No_Messages_If_No_Subscription() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); + var receivedMessages = new List(); - var client = await testEnvironment.ConnectClient(); - var receivedMessages = new List(); + client.ConnectedAsync += async _ => + { + await client.PublishStringAsync("Connected").ConfigureAwait(false); + }; - client.ConnectedAsync += async e => - { - await client.PublishStringAsync("Connected"); - }; + client.ApplicationMessageReceivedAsync += e => + { + lock (receivedMessages) + { + receivedMessages.Add(e.ApplicationMessage); + } - client.ApplicationMessageReceivedAsync += e => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + await Task.Delay(500); - await Task.Delay(500); + await client.PublishStringAsync("Hello"); - await client.PublishStringAsync("Hello"); + await Task.Delay(500); - await Task.Delay(500); + Assert.AreEqual(0, receivedMessages.Count); + } - Assert.AreEqual(0, receivedMessages.Count); - } - } + [TestMethod] + public async Task Persist_Retained_Message() + { + using var testEnvironment = CreateTestEnvironment(); + List savedRetainedMessages = null; - [TestMethod] - public async Task Persist_Retained_Message() + var s = await testEnvironment.StartServer(); + s.RetainedMessageChangedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - List savedRetainedMessages = null; + savedRetainedMessages = e.StoredRetainedMessages; + return CompletedTask.Instance; + }; - var s = await testEnvironment.StartServer(); - s.RetainedMessageChangedAsync += e => - { - savedRetainedMessages = e.StoredRetainedMessages; - return CompletedTask.Instance; - }; + var c1 = await testEnvironment.ConnectClient(); - var c1 = await testEnvironment.ConnectClient(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await Task.Delay(500); - await Task.Delay(500); - - Assert.AreEqual(1, savedRetainedMessages?.Count); - } - } + Assert.AreEqual(1, savedRetainedMessages?.Count); + } - [TestMethod] - public async Task Publish_After_Client_Connects() + [TestMethod] + public async Task Publish_After_Client_Connects() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); + server.ClientConnectedAsync += async _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); - server.ClientConnectedAsync += async e => + await server.InjectApplicationMessage( + new InjectedMqttApplicationMessage( + new MqttApplicationMessage + { + Topic = "/test/1", + PayloadSegment = new ArraySegment("true"u8.ToArray()), + QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce + }) { - await server.InjectApplicationMessage( - new InjectedMqttApplicationMessage( - new MqttApplicationMessage - { - Topic = "/test/1", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("true")), - QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce - }) - { - SenderClientId = "server" - }); - }; + SenderClientId = "server" + }).ConfigureAwait(false); + }; - string receivedTopic = null; + string receivedTopic = null; - var c1 = await testEnvironment.ConnectClient(); - c1.ApplicationMessageReceivedAsync += e => - { - receivedTopic = e.ApplicationMessage.Topic; - return CompletedTask.Instance; - }; + var c1 = await testEnvironment.ConnectClient(); + c1.ApplicationMessageReceivedAsync += e => + { + receivedTopic = e.ApplicationMessage.Topic; + return CompletedTask.Instance; + }; - await c1.SubscribeAsync("#"); + await c1.SubscribeAsync("#"); - await testEnvironment.ConnectClient(); - await testEnvironment.ConnectClient(); - await testEnvironment.ConnectClient(); - await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); - await Task.Delay(500); + await Task.Delay(500); - Assert.AreEqual("/test/1", receivedTopic); - } - } + Assert.AreEqual("/test/1", receivedTopic); + } - [TestMethod] - public async Task Publish_At_Least_Once_0x01() - { - await TestPublishAsync("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, "A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, 1); - } + [TestMethod] + public async Task Publish_At_Least_Once_0x01() + { + await TestPublishAsync("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, "A/B/C", MqttQualityOfServiceLevel.AtLeastOnce, 1).ConfigureAwait(false); + } - [TestMethod] - public async Task Publish_At_Most_Once_0x00() - { - await TestPublishAsync("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "A/B/C", MqttQualityOfServiceLevel.AtMostOnce, 1); - } + [TestMethod] + public async Task Publish_At_Most_Once_0x00() + { + await TestPublishAsync("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "A/B/C", MqttQualityOfServiceLevel.AtMostOnce, 1).ConfigureAwait(false); + } - [TestMethod] - public async Task Publish_Exactly_Once_0x02() - { - await TestPublishAsync("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, "A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, 1); - } + [TestMethod] + public async Task Publish_Exactly_Once_0x02() + { + await TestPublishAsync("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, "A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, 1).ConfigureAwait(false); + } + + [TestMethod] + public async Task Publish_From_Server() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Publish_From_Server() + var receivedMessagesCount = 0; + + var client = await testEnvironment.ConnectClient(); + client.ApplicationMessageReceivedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - var receivedMessagesCount = 0; + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build(); + await client.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - var client = await testEnvironment.ConnectClient(); - client.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + await server.InjectApplicationMessage( + new InjectedMqttApplicationMessage(message) + { + SenderClientId = "server" + }); - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build(); - await client.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + await Task.Delay(1000); - await server.InjectApplicationMessage( - new InjectedMqttApplicationMessage(message) - { - SenderClientId = "server" - }); + Assert.AreEqual(1, receivedMessagesCount); + } - await Task.Delay(1000); + [TestMethod] + public async Task Publish_Multiple_Clients() + { + var receivedMessagesCount = 0; - Assert.AreEqual(1, receivedMessagesCount); - } - } + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - [TestMethod] - public async Task Publish_Multiple_Clients() + var c1 = await testEnvironment.ConnectClient(); + var c2 = await testEnvironment.ConnectClient(); + var c3 = await testEnvironment.ConnectClient(); + + c2.ApplicationMessageReceivedAsync += _ => { - var receivedMessagesCount = 0; + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + c3.ApplicationMessageReceivedAsync += _ => + { + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - var c1 = await testEnvironment.ConnectClient(); - var c2 = await testEnvironment.ConnectClient(); - var c3 = await testEnvironment.ConnectClient(); + await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); + await c3.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); - c2.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build(); - c3.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + for (var i = 0; i < 500; i++) + { + await c1.PublishAsync(message).ConfigureAwait(false); + } - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); - await c3.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); + SpinWait.SpinUntil(() => receivedMessagesCount == 1000, TimeSpan.FromSeconds(20)); - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build(); + Assert.AreEqual(1000, receivedMessagesCount); + } - for (var i = 0; i < 500; i++) - { - await c1.PublishAsync(message).ConfigureAwait(false); - } + [TestMethod] + public async Task Remove_Session() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); - SpinWait.SpinUntil(() => receivedMessagesCount == 1000, TimeSpan.FromSeconds(20)); + var clientOptions = new MqttClientOptionsBuilder(); + var c1 = await testEnvironment.ConnectClient(clientOptions); + await Task.Delay(500); + Assert.AreEqual(1, (await server.GetClientsAsync()).Count); - Assert.AreEqual(1000, receivedMessagesCount); - } - } + await c1.DisconnectAsync(); + await Task.Delay(500); - [TestMethod] - public async Task Remove_Session() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); + Assert.AreEqual(0, (await server.GetClientsAsync()).Count); + } - var clientOptions = new MqttClientOptionsBuilder(); - var c1 = await testEnvironment.ConnectClient(clientOptions); - await Task.Delay(500); - Assert.AreEqual(1, (await server.GetClientsAsync()).Count); + [TestMethod] + public async Task Same_Client_Id_Connect_Disconnect_Event_Order() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - await c1.DisconnectAsync(); - await Task.Delay(500); + var events = new List(); - Assert.AreEqual(0, (await server.GetClientsAsync()).Count); + server.ClientConnectedAsync += _ => + { + lock (events) + { + events.Add("c"); } - } - [TestMethod] - public async Task Same_Client_Id_Connect_Disconnect_Event_Order() + return CompletedTask.Instance; + }; + + server.ClientDisconnectedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) + lock (events) { - var server = await testEnvironment.StartServer(); + events.Add("d"); + } - var events = new List(); + return CompletedTask.Instance; + }; - server.ClientConnectedAsync += e => - { - lock (events) - { - events.Add("c"); - } + var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString()); - return CompletedTask.Instance; - }; + // c + var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder); - server.ClientDisconnectedAsync += e => - { - lock (events) - { - events.Add("d"); - } + await LongTestDelay(); + var flow = string.Join(string.Empty, events); + Assert.AreEqual("c", flow); - return CompletedTask.Instance; - }; + // dc + // Connect client with same client ID. Should disconnect existing client. + var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder); - var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString()); + await LongTestDelay(); + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdc", flow); - // c - var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder); + c2.ApplicationMessageReceivedAsync += _ => + { + lock (events) + { + events.Add("r"); + } - await LongTestDelay(); - var flow = string.Join(string.Empty, events); - Assert.AreEqual("c", flow); + return CompletedTask.Instance; + }; - // dc - // Connect client with same client ID. Should disconnect existing client. - var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder); + await c2.SubscribeAsync("topic"); - await LongTestDelay(); - flow = string.Join(string.Empty, events); - Assert.AreEqual("cdc", flow); + // r + await c2.PublishStringAsync("topic"); - c2.ApplicationMessageReceivedAsync += e => - { - lock (events) - { - events.Add("r"); - } + await LongTestDelay(); + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdcr", flow); - return CompletedTask.Instance; - }; + // nothing - await c2.SubscribeAsync("topic"); + Assert.AreEqual(false, c1.IsConnected); + await c1.TryDisconnectAsync(); + Assert.AreEqual(false, c1.IsConnected); - // r - await c2.PublishStringAsync("topic"); + await LongTestDelay(); - await LongTestDelay(); - flow = string.Join(string.Empty, events); - Assert.AreEqual("cdcr", flow); + // d + Assert.AreEqual(true, c2.IsConnected); + await c2.DisconnectAsync(); - // nothing + await LongTestDelay(); - Assert.AreEqual(false, c1.IsConnected); - await c1.TryDisconnectAsync(); - Assert.AreEqual(false, c1.IsConnected); + await server.StopAsync(); - await LongTestDelay(); + await LongTestDelay(); + flow = string.Join(string.Empty, events); + Assert.AreEqual("cdcrd", flow); + } - // d - Assert.AreEqual(true, c2.IsConnected); - await c2.DisconnectAsync(); + [TestMethod] + public async Task Same_Client_Id_Refuse_Connection() + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; - await LongTestDelay(); + _connected = []; - await server.StopAsync(); + var server = await testEnvironment.StartServer(); - await LongTestDelay(); - flow = string.Join(string.Empty, events); - Assert.AreEqual("cdcrd", flow); - } - } + server.ValidatingConnectionAsync += e => + { + ConnectionValidationHandler(e); + return CompletedTask.Instance; + }; - [TestMethod] - public async Task Same_Client_Id_Refuse_Connection() + var events = new List(); + + server.ClientConnectedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) + lock (events) { - testEnvironment.IgnoreClientLogErrors = true; + events.Add("c"); + } - _connected = new Dictionary(); + return CompletedTask.Instance; + }; - var server = await testEnvironment.StartServer(); + server.ClientDisconnectedAsync += _ => + { + lock (events) + { + events.Add("d"); + } - server.ValidatingConnectionAsync += e => - { - ConnectionValidationHandler(e); - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - var events = new List(); + var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id"); - server.ClientConnectedAsync += e => - { - lock (events) - { - events.Add("c"); - } + // c + var c1 = await testEnvironment.ConnectClient(clientOptions); - return CompletedTask.Instance; - }; + c1.DisconnectedAsync += _ => + { + lock (events) + { + events.Add("x"); + } - server.ClientDisconnectedAsync += e => - { - lock (events) - { - events.Add("d"); - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + c1.ApplicationMessageReceivedAsync += _ => + { + lock (events) + { + events.Add("r"); + } - var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id"); + return CompletedTask.Instance; + }; - // c - var c1 = await testEnvironment.ConnectClient(clientOptions); + c1.SubscribeAsync("topic").Wait(); - c1.DisconnectedAsync += _ => - { - lock (events) - { - events.Add("x"); - } + await Task.Delay(500); - return CompletedTask.Instance; - }; + c1.PublishStringAsync("topic").Wait(); - c1.ApplicationMessageReceivedAsync += e => - { - lock (events) - { - events.Add("r"); - } + await Task.Delay(500); - return CompletedTask.Instance; - }; + var flow = string.Join(string.Empty, events); + Assert.AreEqual("cr", flow); - c1.SubscribeAsync("topic").Wait(); + try + { + await testEnvironment.ConnectClient(clientOptions); + Assert.Fail("same id connection is expected to fail"); + } + catch + { + //same id connection is expected to fail + } - await Task.Delay(500); + await Task.Delay(500); - c1.PublishStringAsync("topic").Wait(); + flow = string.Join(string.Empty, events); + Assert.AreEqual("cr", flow); - await Task.Delay(500); + c1.PublishStringAsync("topic").Wait(); - var flow = string.Join(string.Empty, events); - Assert.AreEqual("cr", flow); + await Task.Delay(500); - try - { - await testEnvironment.ConnectClient(clientOptions); - Assert.Fail("same id connection is expected to fail"); - } - catch - { - //same id connection is expected to fail - } + flow = string.Join(string.Empty, events); + Assert.AreEqual("crr", flow); + } - await Task.Delay(500); + [TestMethod] + public async Task Send_Long_Body() + { + using var testEnvironment = CreateTestEnvironment(); + const int PayloadSizeInMB = 30; + const int CharCount = PayloadSizeInMB * 1024 * 1024; - flow = string.Join(string.Empty, events); - Assert.AreEqual("cr", flow); + var longBody = new byte[CharCount]; + byte @char = 32; - c1.PublishStringAsync("topic").Wait(); + for (long i = 0; i < PayloadSizeInMB * 1024L * 1024L; i++) + { + longBody[i] = @char; - await Task.Delay(500); + @char++; - flow = string.Join(string.Empty, events); - Assert.AreEqual("crr", flow); + if (@char > 126) + { + @char = 32; } } - [TestMethod] - public async Task Send_Long_Body() - { - using (var testEnvironment = CreateTestEnvironment()) - { - const int PayloadSizeInMB = 30; - const int CharCount = PayloadSizeInMB * 1024 * 1024; + byte[] receivedBody = null; - var longBody = new byte[CharCount]; - byte @char = 32; - - for (long i = 0; i < PayloadSizeInMB * 1024L * 1024L; i++) - { - longBody[i] = @char; + await testEnvironment.StartServer(); - @char++; - - if (@char > 126) - { - @char = 32; - } - } + var client1 = await testEnvironment.ConnectClient(); + client1.ApplicationMessageReceivedAsync += e => + { + receivedBody = e.ApplicationMessage.Payload.ToArray(); + return CompletedTask.Instance; + }; - byte[] receivedBody = null; + await client1.SubscribeAsync("string"); - await testEnvironment.StartServer(); + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishBinaryAsync("string", longBody); - var client1 = await testEnvironment.ConnectClient(); - client1.ApplicationMessageReceivedAsync += e => - { - receivedBody = e.ApplicationMessage.Payload.ToArray(); - return CompletedTask.Instance; - }; + await Task.Delay(TimeSpan.FromSeconds(5)); - await client1.SubscribeAsync("string"); + Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? [])); + } - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishBinaryAsync("string", longBody); + [TestMethod] + public async Task Set_Subscription_At_Server() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - await Task.Delay(TimeSpan.FromSeconds(5)); + server.ClientConnectedAsync += async e => + { + // Every client will automatically subscribe to this topic. + await server.SubscribeAsync(e.ClientId, "topic1").ConfigureAwait(false); + }; - Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0])); - } - } + var client = await testEnvironment.ConnectClient(); + var receivedMessages = new List(); - [TestMethod] - public async Task Set_Subscription_At_Server() + client.ApplicationMessageReceivedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) + lock (receivedMessages) { - var server = await testEnvironment.StartServer(); - - server.ClientConnectedAsync += async e => - { - // Every client will automatically subscribe to this topic. - await server.SubscribeAsync(e.ClientId, "topic1"); - }; + receivedMessages.Add(e.ApplicationMessage); + } - var client = await testEnvironment.ConnectClient(); - var receivedMessages = new List(); + return CompletedTask.Instance; + }; - client.ApplicationMessageReceivedAsync += e => - { - lock (receivedMessages) - { - receivedMessages.Add(e.ApplicationMessage); - } + await Task.Delay(500); - return CompletedTask.Instance; - }; + await client.PublishStringAsync("Hello"); + await Task.Delay(100); + Assert.AreEqual(0, receivedMessages.Count); - await Task.Delay(500); + await client.PublishStringAsync("topic1"); + await Task.Delay(100); + Assert.AreEqual(1, receivedMessages.Count); + } - await client.PublishStringAsync("Hello"); - await Task.Delay(100); - Assert.AreEqual(0, receivedMessages.Count); + [TestMethod] + public async Task Shutdown_Disconnects_Clients_Gracefully() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); - await client.PublishStringAsync("topic1"); - await Task.Delay(100); - Assert.AreEqual(1, receivedMessages.Count); - } - } + var disconnectCalled = 0; - [TestMethod] - public async Task Shutdown_Disconnects_Clients_Gracefully() + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); + c1.DisconnectedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); + disconnectCalled++; + return CompletedTask.Instance; + }; - var disconnectCalled = 0; + // Stopping the server should disconnect the connection with the client + // which, it turn, will fire the disconnected event at the client. + await server.StopAsync(); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); - c1.DisconnectedAsync += e => - { - disconnectCalled++; - return CompletedTask.Instance; - }; + await LongTestDelay(); + + Assert.AreEqual(1, disconnectCalled); + } - // Stopping the server should disconnect the connection with the client - // which, it turn, will fire the disconnected event at the client. - await server.StopAsync(); + [TestMethod] + public async Task Stop_And_Restart() + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; - await LongTestDelay(); + var server = await testEnvironment.StartServer(); - Assert.AreEqual(1, disconnectCalled); - } - } + await testEnvironment.ConnectClient(); + await server.StopAsync(); - [TestMethod] - public async Task Stop_And_Restart() + try { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; + await testEnvironment.ConnectClient(); + Assert.Fail("Connecting should fail."); + } + catch + { + // Ignore errors. + } - var server = await testEnvironment.StartServer(); + await server.StartAsync(); - await testEnvironment.ConnectClient(); - await server.StopAsync(); + // The client should be able to connect again. + await testEnvironment.ConnectClient(); + } - try - { - await testEnvironment.ConnectClient(); - Assert.Fail("Connecting should fail."); - } - catch - { - // Ignore errors. - } + [TestMethod] + [DataRow("", null)] + [DataRow("", "")] + [DataRow(null, null)] + public async Task Use_Admissible_Credentials(string username, string password) + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - await server.StartAsync(); + var client = testEnvironment.CreateClient(); - // The client should be able to connect again. - await testEnvironment.ConnectClient(); - } - } + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCredentials(username, password).Build(); - [TestMethod] - [DataRow("", null)] - [DataRow("", "")] - [DataRow(null, null)] - public async Task Use_Admissible_Credentials(string username, string password) - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + var connectResult = await client.ConnectAsync(clientOptions); - var client = testEnvironment.CreateClient(); + Assert.IsFalse(connectResult.IsSessionPresent); + Assert.IsTrue(client.IsConnected); + } - var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCredentials(username, password).Build(); + [TestMethod] + public async Task Use_Empty_Client_ID() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + var client2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("b").WithCleanSession(false)); + await client2.PublishStringAsync("x", "1"); + await client2.PublishStringAsync("x", "2"); + await client2.PublishStringAsync("x", "3"); + await client2.DisconnectAsync(); - var connectResult = await client.ConnectAsync(clientOptions); + var client = testEnvironment.CreateClient(); - Assert.IsFalse(connectResult.IsSessionPresent); - Assert.IsTrue(client.IsConnected); - } - } + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithClientId(string.Empty).Build(); - [TestMethod] - public async Task Use_Empty_Client_ID() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - var client2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("b").WithCleanSession(false)); - await client2.PublishStringAsync("x", "1"); - await client2.PublishStringAsync("x", "2"); - await client2.PublishStringAsync("x", "3"); - await client2.DisconnectAsync(); + var connectResult = await client.ConnectAsync(clientOptions); - var client = testEnvironment.CreateClient(); + Assert.IsFalse(connectResult.IsSessionPresent); + Assert.IsTrue(client.IsConnected); + } - var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithClientId(string.Empty).Build(); + [TestMethod] + public async Task Disconnect_Client_with_Reason() + { + using var testEnvironment = CreateTestEnvironment(); + var disconnectPacketReceived = false; - var connectResult = await client.ConnectAsync(clientOptions); + string testClientId = null; - Assert.IsFalse(connectResult.IsSessionPresent); - Assert.IsTrue(client.IsConnected); - } - } + await testEnvironment.StartServer(); - [TestMethod] - public async Task Disconnect_Client_with_Reason() + testEnvironment.Server.ClientConnectedAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var disconnectPacketReceived = false; - - string testClientId = null; + testClientId = e.ClientId; + return CompletedTask.Instance; + }; - await testEnvironment.StartServer(); - - testEnvironment.Server.ClientConnectedAsync += e => - { - testClientId = e.ClientId; - return CompletedTask.Instance; - }; + var client = testEnvironment.CreateClient(); - var client = testEnvironment.CreateClient(); - - client.InspectPacketAsync += e => + client.InspectPacketAsync += e => + { + if (e.Buffer.Length > 0) + { + if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4) { - if (e.Buffer.Length > 0) - { - if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4) - { - disconnectPacketReceived = true; - } - } - return CompletedTask.Instance; - }; + disconnectPacketReceived = true; + } + } + return CompletedTask.Instance; + }; - await client.ConnectAsync( - new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort) - .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311) - .Build() - ); + await client.ConnectAsync( + new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort) + .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311) + .Build() + ); - await LongTestDelay(); + await LongTestDelay(); - // Test client should be connected now + // Test client should be connected now - Assert.IsTrue(testClientId != null); + Assert.IsTrue(testClientId != null); - // Have the server disconnect the client with SessionTakenOver reason; no DISCONNECT should be sent to MQTT 3.1.1 clients + // 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 testEnvironment.Server.DisconnectClientAsync(testClientId, MqttDisconnectReasonCode.SessionTakenOver); - await LongTestDelay(); + await LongTestDelay(); - // MQTT 3.1.1 client should not receive a disconnect packet + // MQTT 3.1.1 client should not receive a disconnect packet - Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected"); - } - } + Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected"); + } - void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs) + void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs) + { + if (!_connected.TryAdd(eventArgs.ClientId, true)) { - if (_connected.ContainsKey(eventArgs.ClientId)) - { - eventArgs.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; - return; - } - - _connected[eventArgs.ClientId] = true; - eventArgs.ReasonCode = MqttConnectReasonCode.Success; + eventArgs.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + return; } - async Task TestPublishAsync( - string topic, - MqttQualityOfServiceLevel qualityOfServiceLevel, - string topicFilter, - MqttQualityOfServiceLevel filterQualityOfServiceLevel, - int expectedReceivedMessagesCount) - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + eventArgs.ReasonCode = MqttConnectReasonCode.Success; + } - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("receiver")); - var c1MessageHandler = testEnvironment.CreateApplicationMessageHandler(c1); - await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); + async Task TestPublishAsync( + string topic, + MqttQualityOfServiceLevel qualityOfServiceLevel, + string topicFilter, + MqttQualityOfServiceLevel filterQualityOfServiceLevel, + int expectedReceivedMessagesCount) + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("sender")); - await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); - await Task.Delay(500); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("receiver")); + var c1MessageHandler = testEnvironment.CreateApplicationMessageHandler(c1); + await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); - await c2.DisconnectAsync().ConfigureAwait(false); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("sender")); + await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload([]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); + await Task.Delay(500); - await Task.Delay(500); - await c1.UnsubscribeAsync(topicFilter); - await Task.Delay(500); + await c2.DisconnectAsync().ConfigureAwait(false); - Assert.AreEqual(expectedReceivedMessagesCount, c1MessageHandler.ReceivedEventArgs.Count); - } - } + await Task.Delay(500); + await c1.UnsubscribeAsync(topicFilter); + await Task.Delay(500); + + Assert.AreEqual(expectedReceivedMessagesCount, c1MessageHandler.ReceivedEventArgs.Count); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/HotSwapCerts_Tests.cs b/Source/MQTTnet.Tests/Server/HotSwapCerts_Tests.cs index 95caa2600..74b695515 100644 --- a/Source/MQTTnet.Tests/Server/HotSwapCerts_Tests.cs +++ b/Source/MQTTnet.Tests/Server/HotSwapCerts_Tests.cs @@ -15,403 +15,390 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; +// missing certificate builder api means tests won't work for older frameworks + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class HotSwapCerts_Tests { - // missing certificate builder api means tests won't work for older frameworks + static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); - [TestClass] - public sealed class HotSwapCerts_Tests + [TestMethod] + public async Task ClientCertChangeWithoutServerUpdateFailsReconnect() { - static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); + using var server = new ServerTestHarness(); + using var client01 = new ClientTestHarness(); + server.InstallNewClientCert(client01.GetCurrentClientCert()); + client01.InstallNewServerCert(server.GetCurrentServerCert()); - [TestMethod] - public async Task ClientCertChangeWithoutServerUpdateFailsReconnect() - { - using (var server = new ServerTestHarness()) - using (var client01 = new ClientTestHarness()) - { - server.InstallNewClientCert(client01.GetCurrentClientCert()); - client01.InstallNewServerCert(server.GetCurrentServerCert()); + await server.StartServer(); - await server.StartServer(); + await client01.Connect(); - await client01.Connect(); + client01.WaitForConnectOrFail(DefaultTimeout); - client01.WaitForConnectOrFail(DefaultTimeout); + client01.HotSwapClientCert(); + server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); + client01.WaitForDisconnectOrFail(DefaultTimeout); - client01.HotSwapClientCert(); - server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); - client01.WaitForDisconnectOrFail(DefaultTimeout); + client01.WaitForConnectToFail(DefaultTimeout); + } - client01.WaitForConnectToFail(DefaultTimeout); - } - } + [TestMethod] + public async Task ClientCertChangeWithServerUpdateAcceptsReconnect() + { + using var server = new ServerTestHarness(); + using var client01 = new ClientTestHarness(); + server.InstallNewClientCert(client01.GetCurrentClientCert()); + client01.InstallNewServerCert(server.GetCurrentServerCert()); - [TestMethod] - public async Task ClientCertChangeWithServerUpdateAcceptsReconnect() - { - using (var server = new ServerTestHarness()) - using (var client01 = new ClientTestHarness()) - { - server.InstallNewClientCert(client01.GetCurrentClientCert()); - client01.InstallNewServerCert(server.GetCurrentServerCert()); + await server.StartServer(); - await server.StartServer(); + await client01.Connect(); - await client01.Connect(); + client01.WaitForConnectOrFail(DefaultTimeout); - client01.WaitForConnectOrFail(DefaultTimeout); + client01.HotSwapClientCert(); + server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); + client01.WaitForDisconnectOrFail(DefaultTimeout); - client01.HotSwapClientCert(); - server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); - client01.WaitForDisconnectOrFail(DefaultTimeout); + server.InstallNewClientCert(client01.GetCurrentClientCert()); - server.InstallNewClientCert(client01.GetCurrentClientCert()); + client01.WaitForConnectOrFail(DefaultTimeout); + } - client01.WaitForConnectOrFail(DefaultTimeout); - } - } + [TestMethod] + public async Task ServerCertChangeWithClientCertUpdateAllowsReconnect() + { + using var server = new ServerTestHarness(); + using var client01 = new ClientTestHarness(); + server.InstallNewClientCert(client01.GetCurrentClientCert()); + client01.InstallNewServerCert(server.GetCurrentServerCert()); - [TestMethod] - public async Task ServerCertChangeWithClientCertUpdateAllowsReconnect() - { - using (var server = new ServerTestHarness()) - using (var client01 = new ClientTestHarness()) - { - server.InstallNewClientCert(client01.GetCurrentClientCert()); - client01.InstallNewServerCert(server.GetCurrentServerCert()); + await server.StartServer(); + await client01.Connect(); - await server.StartServer(); - await client01.Connect(); + client01.WaitForConnectOrFail(DefaultTimeout); + server.HotSwapServerCert(); - client01.WaitForConnectOrFail(DefaultTimeout); - server.HotSwapServerCert(); + server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); + client01.WaitForDisconnectOrFail(DefaultTimeout); + client01.InstallNewServerCert(server.GetCurrentServerCert()); - server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); - client01.WaitForDisconnectOrFail(DefaultTimeout); - client01.InstallNewServerCert(server.GetCurrentServerCert()); + client01.WaitForConnectOrFail(DefaultTimeout); + } - client01.WaitForConnectOrFail(DefaultTimeout); - } - } + [TestMethod] + public async Task ServerCertChangeWithoutClientCertUpdateFailsReconnect() + { + using var server = new ServerTestHarness(); + using var client01 = new ClientTestHarness(); + server.InstallNewClientCert(client01.GetCurrentClientCert()); + client01.InstallNewServerCert(server.GetCurrentServerCert()); - [TestMethod] - public async Task ServerCertChangeWithoutClientCertUpdateFailsReconnect() - { - using (var server = new ServerTestHarness()) - using (var client01 = new ClientTestHarness()) - { - server.InstallNewClientCert(client01.GetCurrentClientCert()); - client01.InstallNewServerCert(server.GetCurrentServerCert()); + await server.StartServer(); + await client01.Connect(); - await server.StartServer(); - await client01.Connect(); + client01.WaitForConnectOrFail(DefaultTimeout); + server.HotSwapServerCert(); - client01.WaitForConnectOrFail(DefaultTimeout); - server.HotSwapServerCert(); + server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); + client01.WaitForDisconnectOrFail(DefaultTimeout); - server.ForceDisconnectAsync(client01).Wait(DefaultTimeout); - client01.WaitForDisconnectOrFail(DefaultTimeout); + client01.WaitForConnectToFail(DefaultTimeout); + } - client01.WaitForConnectToFail(DefaultTimeout); - } - } + static X509Certificate2 CreateSelfSignedCertificate(string oid) + { + var sanBuilder = new SubjectAlternativeNameBuilder(); + sanBuilder.AddIpAddress(IPAddress.Loopback); + sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); + sanBuilder.AddDnsName("localhost"); - static X509Certificate2 CreateSelfSignedCertificate(string oid) - { - var sanBuilder = new SubjectAlternativeNameBuilder(); - sanBuilder.AddIpAddress(IPAddress.Loopback); - sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); - sanBuilder.AddDnsName("localhost"); + using var rsa = RSA.Create(); + var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); - using (var rsa = RSA.Create()) - { - var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); + certRequest.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); - certRequest.CertificateExtensions.Add( - new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); + certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false)); - certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false)); + certRequest.CertificateExtensions.Add(sanBuilder.Build()); - certRequest.CertificateExtensions.Add(sanBuilder.Build()); + using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)); + var pfxCertificate = new X509Certificate2( + certificate.Export(X509ContentType.Pfx), + (string)null, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); - using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10))) - { - var pfxCertificate = new X509Certificate2( - certificate.Export(X509ContentType.Pfx), - (string)null, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); + return pfxCertificate; + } - return pfxCertificate; - } - } + sealed class ClientTestHarness : IDisposable + { + readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider(); + + IMqttClient _client; + + public string ClientId => _client.Options.ClientId; + + public Task Connect() + { + return Run_Client_Connection(); } - sealed class ClientTestHarness : IDisposable + public void Dispose() { - readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider(); + _client.Dispose(); + _hotSwapClient.Dispose(); + } - IMqttClient _client; + public X509Certificate2 GetCurrentClientCert() + { + var result = _hotSwapClient.GetCertificates()[0]; + return new X509Certificate2(result); + } - public string ClientId => _client.Options.ClientId; + public void HotSwapClientCert() + { + _hotSwapClient.HotSwapCert(); + } - public Task Connect() - { - return Run_Client_Connection(); - } + public void InstallNewServerCert(X509Certificate2 serverCert) + { + _hotSwapClient.InstallNewServerCert(serverCert); + } - public void Dispose() - { - _client.Dispose(); - _hotSwapClient.Dispose(); - } + public void WaitForConnectOrFail(TimeSpan timeout) + { + Thread.Sleep(100); - public X509Certificate2 GetCurrentClientCert() + if (!_client.IsConnected) { - var result = _hotSwapClient.GetCertificates()[0]; - return new X509Certificate2(result); + _client.ReconnectAsync().Wait(timeout); } - public void HotSwapClientCert() - { - _hotSwapClient.HotSwapCert(); - } + WaitForConnect(timeout); - public void InstallNewServerCert(X509Certificate2 serverCert) - { - _hotSwapClient.InstallNewServerCert(serverCert); - } + Assert.IsNotNull(_client, "Client was never initialized"); + Assert.IsTrue(_client.IsConnected, $"Client connection failed after {timeout}"); + } - public void WaitForConnectOrFail(TimeSpan timeout) - { - Thread.Sleep(100); + public void WaitForConnectToFail(TimeSpan timeout) + { + Assert.IsFalse(_client.IsConnected, "Client should be disconnected before waiting for connect."); - if (!_client.IsConnected) - { - _client.ReconnectAsync().Wait(timeout); - } + WaitForConnect(timeout); - WaitForConnect(timeout); + Assert.IsNotNull(_client, "Client was never initialized"); + Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail"); + } - Assert.IsNotNull(_client, "Client was never initialized"); - Assert.IsTrue(_client.IsConnected, $"Client connection failed after {timeout}"); + void WaitForDisconnect(TimeSpan timeout) + { + var timer = Stopwatch.StartNew(); + while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout) + { + Thread.Sleep(100); } + } - public void WaitForConnectToFail(TimeSpan timeout) - { - Assert.IsFalse(_client.IsConnected, "Client should be disconnected before waiting for connect."); + public void WaitForDisconnectOrFail(TimeSpan timeout) + { + WaitForDisconnect(timeout); - WaitForConnect(timeout); + Assert.IsNotNull(_client, "Client was never initialized"); + Assert.IsFalse(_client.IsConnected, $"Client connection should have disconnected after {timeout}"); + } - Assert.IsNotNull(_client, "Client was never initialized"); - Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail"); - } + async Task Run_Client_Connection() + { + var optionsBuilder = new MqttClientOptionsBuilder() + .WithTlsOptions( + o => o.WithClientCertificatesProvider(_hotSwapClient) + .WithCertificateValidationHandler(_hotSwapClient.OnCertificateValidation) + .WithSslProtocols(SslProtocols.Tls12)) + .WithTcpServer("localhost") + .WithCleanSession() + .WithProtocolVersion(MqttProtocolVersion.V500); + + var mqttClientOptions = optionsBuilder.Build(); + + var factory = new MqttClientFactory(); + var mqttClient = factory.CreateMqttClient(); + _client = mqttClient; + + await mqttClient.ConnectAsync(mqttClientOptions); + } - void WaitForDisconnect(TimeSpan timeout) + void WaitForConnect(TimeSpan timeout) + { + var timer = Stopwatch.StartNew(); + while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout) { - var timer = Stopwatch.StartNew(); - while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout) - { - Thread.Sleep(100); - } + Thread.Sleep(100); } + } + } - public void WaitForDisconnectOrFail(TimeSpan timeout) - { - WaitForDisconnect(timeout); + sealed class ServerTestHarness : IDisposable + { + readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider(); - Assert.IsNotNull(_client, "Client was never initialized"); - Assert.IsFalse(_client.IsConnected, $"Client connection should have disconnected after {timeout}"); - } + MqttServer _server; - async Task Run_Client_Connection() + public void Dispose() + { + if (_server != null) { - var optionsBuilder = new MqttClientOptionsBuilder() - .WithTlsOptions( - o => o.WithClientCertificatesProvider(_hotSwapClient) - .WithCertificateValidationHandler(_hotSwapClient.OnCertificateValidation) - .WithSslProtocols(SslProtocols.Tls12)) - .WithTcpServer("localhost") - .WithCleanSession() - .WithProtocolVersion(MqttProtocolVersion.V500); - - var mqttClientOptions = optionsBuilder.Build(); - - var factory = new MqttClientFactory(); - var mqttClient = factory.CreateMqttClient(); - _client = mqttClient; - - await mqttClient.ConnectAsync(mqttClientOptions); + _server.StopAsync().Wait(); + _server.Dispose(); } - void WaitForConnect(TimeSpan timeout) - { - var timer = Stopwatch.StartNew(); - while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout) - { - Thread.Sleep(100); - } - } + _hotSwapServer?.Dispose(); } - sealed class ServerTestHarness : IDisposable + public Task ForceDisconnectAsync(ClientTestHarness client) { - readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider(); - - MqttServer _server; - - public void Dispose() - { - if (_server != null) - { - _server.StopAsync().Wait(); - _server.Dispose(); - } + return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError); + } - _hotSwapServer?.Dispose(); - } + public X509Certificate2 GetCurrentServerCert() + { + return _hotSwapServer.GetCertificate(); + } - public Task ForceDisconnectAsync(ClientTestHarness client) - { - return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError); - } + public void HotSwapServerCert() + { + _hotSwapServer.HotSwapCert(); + } - public X509Certificate2 GetCurrentServerCert() - { - return _hotSwapServer.GetCertificate(); - } + public void InstallNewClientCert(X509Certificate2 serverCert) + { + _hotSwapServer.InstallNewClientCert(serverCert); + } - public void HotSwapServerCert() - { - _hotSwapServer.HotSwapCert(); - } + public Task StartServer() + { + var mqttServerFactory = new MqttServerFactory(); - public void InstallNewClientCert(X509Certificate2 serverCert) - { - _hotSwapServer.InstallNewClientCert(serverCert); - } + var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(_hotSwapServer) + .WithRemoteCertificateValidationCallback(_hotSwapServer.RemoteCertificateValidationCallback) + .WithEncryptedEndpoint() + .Build(); - public Task StartServer() - { - var mqttServerFactory = new MqttServerFactory(); + mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true; - var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(_hotSwapServer) - .WithRemoteCertificateValidationCallback(_hotSwapServer.RemoteCertificateValidationCallback) - .WithEncryptedEndpoint() - .Build(); + _server = mqttServerFactory.CreateMqttServer(mqttServerOptions); + return _server.StartAsync(); + } + } - mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true; + class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable + { + X509Certificate2Collection _certificates; + ConcurrentBag _serverCerts = new ConcurrentBag(); - _server = mqttServerFactory.CreateMqttServer(mqttServerOptions); - return _server.StartAsync(); - } + public HotSwappableClientCertProvider() + { + _certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2")); } - class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable + public void Dispose() { - X509Certificate2Collection _certificates; - ConcurrentBag _serverCerts = new ConcurrentBag(); - - public HotSwappableClientCertProvider() - { - _certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2")); - } - - public void Dispose() + if (_certificates != null) { - if (_certificates != null) + foreach (var certs in _certificates) { - foreach (var certs in _certificates) - { - certs.Dispose(); - } + certs.Dispose(); } } + } - public X509CertificateCollection GetCertificates() - { - return new X509Certificate2Collection(_certificates); - } + public X509CertificateCollection GetCertificates() + { + return new X509Certificate2Collection(_certificates); + } - public void HotSwapCert() - { - _certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2")); - } + public void HotSwapCert() + { + _certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2")); + } - public void InstallNewServerCert(X509Certificate2 serverCert) - { - _serverCerts.Add(serverCert); - } + public void InstallNewServerCert(X509Certificate2 serverCert) + { + _serverCerts.Add(serverCert); + } + + public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext) + { + var serverCerts = _serverCerts.ToArray(); - public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext) + var providedCert = certContext.Certificate.GetRawCertData(); + for (int i = 0, n = serverCerts.Length; i < n; i++) { - var serverCerts = _serverCerts.ToArray(); + var currentcert = serverCerts[i]; - var providedCert = certContext.Certificate.GetRawCertData(); - for (int i = 0, n = serverCerts.Length; i < n; i++) + if (currentcert.RawData.SequenceEqual(providedCert)) { - var currentcert = serverCerts[i]; - - if (currentcert.RawData.SequenceEqual(providedCert)) - { - return true; - } + return true; } - - return false; } + + return false; } + } + + sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable + { + readonly ConcurrentBag _clientCerts = new ConcurrentBag(); + X509Certificate2 _certificate; - sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable + public HotSwappableServerCertProvider() { - readonly ConcurrentBag _clientCerts = new ConcurrentBag(); - X509Certificate2 _certificate; + _certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1"); + } - public HotSwappableServerCertProvider() - { - _certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1"); - } + public void Dispose() + { + _certificate.Dispose(); + } - public void Dispose() - { - _certificate.Dispose(); - } + public X509Certificate2 GetCertificate() + { + return _certificate; + } - public X509Certificate2 GetCertificate() - { - return _certificate; - } + public void HotSwapCert() + { + var newCert = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1"); + var oldCert = Interlocked.Exchange(ref _certificate, newCert); - public void HotSwapCert() - { - var newCert = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1"); - var oldCert = Interlocked.Exchange(ref _certificate, newCert); + oldCert.Dispose(); + } - oldCert.Dispose(); - } + public void InstallNewClientCert(X509Certificate2 certificate) + { + _clientCerts.Add(certificate); + } - public void InstallNewClientCert(X509Certificate2 certificate) - { - _clientCerts.Add(certificate); - } + public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + var serverCerts = _clientCerts.ToArray(); - public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + var providedCert = certificate.GetRawCertData(); + for (int i = 0, n = serverCerts.Length; i < n; i++) { - var serverCerts = _clientCerts.ToArray(); + var currentCert = serverCerts[i]; - var providedCert = certificate.GetRawCertData(); - for (int i = 0, n = serverCerts.Length; i < n; i++) + if (currentCert.RawData.SequenceEqual(providedCert)) { - var currentCert = serverCerts[i]; - - if (currentCert.RawData.SequenceEqual(providedCert)) - { - return true; - } + return true; } - - return false; } + + return false; } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Injection_Tests.cs b/Source/MQTTnet.Tests/Server/Injection_Tests.cs index 85ac53414..bc13fd564 100644 --- a/Source/MQTTnet.Tests/Server/Injection_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Injection_Tests.cs @@ -7,449 +7,449 @@ using MQTTnet.Server; using MQTTnet.Server.Exceptions; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Injection_Tests : BaseTestClass { - [TestClass] - public sealed class Injection_Tests : BaseTestClass + [TestMethod] + public async Task Enqueue_Application_Message_At_Session_Level() { - [TestMethod] - public async Task Enqueue_Application_Message_At_Session_Level() - { - using var testEnvironment = CreateTestEnvironment(); + using var testEnvironment = CreateTestEnvironment(); - var server = await testEnvironment.StartServer(); - var receiver1 = await testEnvironment.ConnectClient(); - var receiver2 = await testEnvironment.ConnectClient(); - var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1); - var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2); + var server = await testEnvironment.StartServer(); + var receiver1 = await testEnvironment.ConnectClient(); + var receiver2 = await testEnvironment.ConnectClient(); + var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1); + var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2); - var status = await server.GetSessionsAsync(); - var clientStatus = status[0]; + var status = await server.GetSessionsAsync(); + var clientStatus = status[0]; - await receiver1.SubscribeAsync("#"); - await receiver2.SubscribeAsync("#"); + await receiver1.SubscribeAsync("#"); + await receiver2.SubscribeAsync("#"); - var message = new MqttApplicationMessageBuilder() - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .WithTopic("InjectedOne").Build(); + var message = new MqttApplicationMessageBuilder() + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithTopic("InjectedOne").Build(); - var enqueued = clientStatus.TryEnqueueApplicationMessage(message, out var injectResult); + var enqueued = clientStatus.TryEnqueueApplicationMessage(message, out var injectResult); - Assert.IsTrue(enqueued); + Assert.IsTrue(enqueued); - await LongTestDelay(); + await LongTestDelay(); - Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count); - Assert.AreEqual(injectResult.PacketIdentifier, messageReceivedHandler1.ReceivedEventArgs[0].PacketIdentifier); - Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic); + Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count); + Assert.AreEqual(injectResult.PacketIdentifier, messageReceivedHandler1.ReceivedEventArgs[0].PacketIdentifier); + Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic); - // The second receiver should NOT receive the message. - Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count); - } + // The second receiver should NOT receive the message. + Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count); + } - [TestMethod] - public async Task Enqueue_Application_Message_At_Session_Level_QueueOverflow_DropNewMessageStrategy() - { - using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false); + [TestMethod] + public async Task Enqueue_Application_Message_At_Session_Level_QueueOverflow_DropNewMessageStrategy() + { + using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false); - var server = await testEnvironment.StartServer( - builder => builder - .WithMaxPendingMessagesPerClient(1) - .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage)); + var server = await testEnvironment.StartServer( + builder => builder + .WithMaxPendingMessagesPerClient(1) + .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage)); - var receiver = await testEnvironment.ConnectClient(); + var receiver = await testEnvironment.ConnectClient(); - var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); - server.InterceptingOutboundPacketAsync += async args => - { - // - The first message is dequeued normally and calls this delay - // - The second message fills the outbound queue - // - The third message overflows the outbound queue - if (args.Packet is MqttPublishPacket) - { - firstMessageOutboundPacketInterceptedTcs.SetResult(); - await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); - } - }; - - var firstMessageEvicted = false; - var secondMessageEvicted = false; - var thirdMessageEvicted = false; - - server.QueuedApplicationMessageOverwrittenAsync += args => + var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); + server.InterceptingOutboundPacketAsync += async args => + { + // - The first message is dequeued normally and calls this delay + // - The second message fills the outbound queue + // - The third message overflows the outbound queue + if (args.Packet is MqttPublishPacket) { - if (args.Packet is not MqttPublishPacket publishPacket) - { - return Task.CompletedTask; - } - - switch (publishPacket.Topic) - { - case "InjectedOne": - firstMessageEvicted = true; - break; - case "InjectedTwo": - secondMessageEvicted = true; - break; - case "InjectedThree": - thirdMessageEvicted = true; - break; - } + firstMessageOutboundPacketInterceptedTcs.SetResult(); + await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); + } + }; - return Task.CompletedTask; - }; - - var status = await server.GetSessionsAsync(); - var clientStatus = status[0]; - await receiver.SubscribeAsync("#"); + var firstMessageEvicted = false; + var secondMessageEvicted = false; + var thirdMessageEvicted = false; - var firstMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( - new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(), out _); - await firstMessageOutboundPacketInterceptedTcs.Task; + server.QueuedApplicationMessageOverwrittenAsync += args => + { + if (args.Packet is not MqttPublishPacket publishPacket) + { + return Task.CompletedTask; + } - var secondMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( - new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build(), out _); + switch (publishPacket.Topic) + { + case "InjectedOne": + firstMessageEvicted = true; + break; + case "InjectedTwo": + secondMessageEvicted = true; + break; + case "InjectedThree": + thirdMessageEvicted = true; + break; + } + + return Task.CompletedTask; + }; + + var status = await server.GetSessionsAsync(); + var clientStatus = status[0]; + await receiver.SubscribeAsync("#"); + + var firstMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( + new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(), out _); + await firstMessageOutboundPacketInterceptedTcs.Task; + + var secondMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( + new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build(), out _); + + var thirdMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( + new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build(), out _); + + // Due to the DropNewMessage strategy the third message will not be enqueued. + // As a result, no existing messages in the queue will be dropped (evicted). + Assert.IsTrue(firstMessageEnqueued); + Assert.IsTrue(secondMessageEnqueued); + Assert.IsFalse(thirdMessageEnqueued); + + Assert.IsFalse(firstMessageEvicted); + Assert.IsFalse(secondMessageEvicted); + Assert.IsFalse(thirdMessageEvicted); + } - var thirdMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( - new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build(), out _); - // Due to the DropNewMessage strategy the third message will not be enqueued. - // As a result, no existing messages in the queue will be dropped (evicted). - Assert.IsTrue(firstMessageEnqueued); - Assert.IsTrue(secondMessageEnqueued); - Assert.IsFalse(thirdMessageEnqueued); + [TestMethod] + public async Task Enqueue_Application_Message_At_Session_Level_QueueOverflow_DropOldestQueuedMessageStrategy() + { + using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false); - Assert.IsFalse(firstMessageEvicted); - Assert.IsFalse(secondMessageEvicted); - Assert.IsFalse(thirdMessageEvicted); - } + var server = await testEnvironment.StartServer( + builder => builder + .WithMaxPendingMessagesPerClient(1) + .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)); + var receiver = await testEnvironment.ConnectClient(); - [TestMethod] - public async Task Enqueue_Application_Message_At_Session_Level_QueueOverflow_DropOldestQueuedMessageStrategy() + var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); + server.InterceptingOutboundPacketAsync += async args => { - using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false); - - var server = await testEnvironment.StartServer( - builder => builder - .WithMaxPendingMessagesPerClient(1) - .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)); + // - The first message is dequeued normally and calls this delay + // - The second message fills the outbound queue + // - The third message overflows the outbound queue + if (args.Packet is MqttPublishPacket) + { + firstMessageOutboundPacketInterceptedTcs.SetResult(); + await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); + } + }; - var receiver = await testEnvironment.ConnectClient(); + var firstMessageEvicted = false; + var secondMessageEvicted = false; + var thirdMessageEvicted = false; - var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); - server.InterceptingOutboundPacketAsync += async args => - { - // - The first message is dequeued normally and calls this delay - // - The second message fills the outbound queue - // - The third message overflows the outbound queue - if (args.Packet is MqttPublishPacket) - { - firstMessageOutboundPacketInterceptedTcs.SetResult(); - await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); - } - }; - - var firstMessageEvicted = false; - var secondMessageEvicted = false; - var thirdMessageEvicted = false; - - server.QueuedApplicationMessageOverwrittenAsync += args => + server.QueuedApplicationMessageOverwrittenAsync += args => + { + if (args.Packet is not MqttPublishPacket publishPacket) { - if (args.Packet is not MqttPublishPacket publishPacket) - { - return Task.CompletedTask; - } - - switch (publishPacket.Topic) - { - case "InjectedOne": - firstMessageEvicted = true; - break; - case "InjectedTwo": - secondMessageEvicted = true; - break; - case "InjectedThree": - thirdMessageEvicted = true; - break; - } - return Task.CompletedTask; - }; + } - var status = await server.GetSessionsAsync(); - var clientStatus = status[0]; - await receiver.SubscribeAsync("#"); + switch (publishPacket.Topic) + { + case "InjectedOne": + firstMessageEvicted = true; + break; + case "InjectedTwo": + secondMessageEvicted = true; + break; + case "InjectedThree": + thirdMessageEvicted = true; + break; + } + + return Task.CompletedTask; + }; + + var status = await server.GetSessionsAsync(); + var clientStatus = status[0]; + await receiver.SubscribeAsync("#"); + + var firstMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( + new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(), out _); + await firstMessageOutboundPacketInterceptedTcs.Task; + + var secondMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( + new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build(), out _); + + var thirdMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( + new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build(), out _); + + // Due to the DropOldestQueuedMessage strategy, all messages will be enqueued initially. + // But the second message will eventually be dropped (evicted) to make room for the third one. + Assert.IsTrue(firstMessageEnqueued); + Assert.IsTrue(secondMessageEnqueued); + Assert.IsTrue(thirdMessageEnqueued); + + Assert.IsFalse(firstMessageEvicted); + Assert.IsTrue(secondMessageEvicted); + Assert.IsFalse(thirdMessageEvicted); + } - var firstMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( - new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(), out _); - await firstMessageOutboundPacketInterceptedTcs.Task; + [TestMethod] + public async Task Deliver_Application_Message_At_Session_Level() + { + using var testEnvironment = CreateTestEnvironment(); - var secondMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( - new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build(), out _); + var server = await testEnvironment.StartServer(); + var receiver1 = await testEnvironment.ConnectClient(); + var receiver2 = await testEnvironment.ConnectClient(); + var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1); + var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2); - var thirdMessageEnqueued = clientStatus.TryEnqueueApplicationMessage( - new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build(), out _); + var status = await server.GetSessionsAsync(); + var clientStatus = status[0]; - // Due to the DropOldestQueuedMessage strategy, all messages will be enqueued initially. - // But the second message will eventually be dropped (evicted) to make room for the third one. - Assert.IsTrue(firstMessageEnqueued); - Assert.IsTrue(secondMessageEnqueued); - Assert.IsTrue(thirdMessageEnqueued); + await receiver1.SubscribeAsync("#"); + await receiver2.SubscribeAsync("#"); - Assert.IsFalse(firstMessageEvicted); - Assert.IsTrue(secondMessageEvicted); - Assert.IsFalse(thirdMessageEvicted); - } + var mqttApplicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("InjectedOne") + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .Build(); + var injectResult = await clientStatus.DeliverApplicationMessageAsync(mqttApplicationMessage); - [TestMethod] - public async Task Deliver_Application_Message_At_Session_Level() - { - using var testEnvironment = CreateTestEnvironment(); + await LongTestDelay(); - var server = await testEnvironment.StartServer(); - var receiver1 = await testEnvironment.ConnectClient(); - var receiver2 = await testEnvironment.ConnectClient(); - var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1); - var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2); + Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count); + Assert.AreEqual(injectResult.PacketIdentifier, messageReceivedHandler1.ReceivedEventArgs[0].PacketIdentifier); + Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic); - var status = await server.GetSessionsAsync(); - var clientStatus = status[0]; + // The second receiver should NOT receive the message. + Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count); + } - await receiver1.SubscribeAsync("#"); - await receiver2.SubscribeAsync("#"); + [TestMethod] + public async Task Deliver_Application_Message_At_Session_Level_QueueOverflow_DropNewMessageStrategy() + { + using var testEnvironment = CreateTestEnvironment(); - var mqttApplicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("InjectedOne") - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .Build(); - var injectResult = await clientStatus.DeliverApplicationMessageAsync(mqttApplicationMessage); + var server = await testEnvironment.StartServer( + builder => builder + .WithMaxPendingMessagesPerClient(1) + .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage)); - await LongTestDelay(); + var receiver = await testEnvironment.ConnectClient(); - Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count); - Assert.AreEqual(injectResult.PacketIdentifier, messageReceivedHandler1.ReceivedEventArgs[0].PacketIdentifier); - Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic); + var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); + server.InterceptingOutboundPacketAsync += async args => + { + // - The first message is dequeued normally and calls this delay + // - The second message fills the outbound queue + // - The third message overflows the outbound queue + if (args.Packet is MqttPublishPacket) + { + firstMessageOutboundPacketInterceptedTcs.SetResult(); + await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); + } + }; - // The second receiver should NOT receive the message. - Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count); - } + var firstMessageEvicted = false; + var secondMessageEvicted = false; + var thirdMessageEvicted = false; - [TestMethod] - public async Task Deliver_Application_Message_At_Session_Level_QueueOverflow_DropNewMessageStrategy() + server.QueuedApplicationMessageOverwrittenAsync += args => { - using var testEnvironment = CreateTestEnvironment(); + if (args.Packet is not MqttPublishPacket publishPacket) + { + return Task.CompletedTask; + } - var server = await testEnvironment.StartServer( - builder => builder - .WithMaxPendingMessagesPerClient(1) - .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage)); + switch (publishPacket.Topic) + { + case "InjectedOne": + firstMessageEvicted = true; + break; + case "InjectedTwo": + secondMessageEvicted = true; + break; + case "InjectedThree": + thirdMessageEvicted = true; + break; + } + + return Task.CompletedTask; + }; + + var status = await server.GetSessionsAsync(); + var clientStatus = status[0]; + await receiver.SubscribeAsync("#"); + + var firstMessageTask = Task.Run( + () => clientStatus.DeliverApplicationMessageAsync( + new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build())); + await LongTestDelay(); + await firstMessageOutboundPacketInterceptedTcs.Task; + + var secondMessageTask = Task.Run( + () => clientStatus.DeliverApplicationMessageAsync( + new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build())); + await LongTestDelay(); + + var thirdMessageTask = Task.Run( + () => clientStatus.DeliverApplicationMessageAsync( + new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build())); + await LongTestDelay(); + + Task.WaitAny(firstMessageTask, secondMessageTask, thirdMessageTask); + + // Due to the DropNewMessage strategy the third message delivery will fail. + // As a result, no existing messages in the queue will be dropped (evicted). + Assert.AreEqual(firstMessageTask.Status, TaskStatus.WaitingForActivation); + Assert.AreEqual(secondMessageTask.Status, TaskStatus.WaitingForActivation); + Assert.AreEqual(thirdMessageTask.Status, TaskStatus.Faulted); + Assert.IsTrue(thirdMessageTask.Exception?.InnerException is MqttPendingMessagesOverflowException); + + Assert.IsFalse(firstMessageEvicted); + Assert.IsFalse(secondMessageEvicted); + Assert.IsFalse(thirdMessageEvicted); + } - var receiver = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task Deliver_Application_Message_At_Session_Level_QueueOverflow_DropOldestQueuedMessageStrategy() + { + using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false); - var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); - server.InterceptingOutboundPacketAsync += async args => - { - // - The first message is dequeued normally and calls this delay - // - The second message fills the outbound queue - // - The third message overflows the outbound queue - if (args.Packet is MqttPublishPacket) - { - firstMessageOutboundPacketInterceptedTcs.SetResult(); - await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); - } - }; - - var firstMessageEvicted = false; - var secondMessageEvicted = false; - var thirdMessageEvicted = false; - - server.QueuedApplicationMessageOverwrittenAsync += args => - { - if (args.Packet is not MqttPublishPacket publishPacket) - { - return Task.CompletedTask; - } - - switch (publishPacket.Topic) - { - case "InjectedOne": - firstMessageEvicted = true; - break; - case "InjectedTwo": - secondMessageEvicted = true; - break; - case "InjectedThree": - thirdMessageEvicted = true; - break; - } + var server = await testEnvironment.StartServer( + builder => builder + .WithMaxPendingMessagesPerClient(1) + .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)); - return Task.CompletedTask; - }; - - var status = await server.GetSessionsAsync(); - var clientStatus = status[0]; - await receiver.SubscribeAsync("#"); - - var firstMessageTask = Task.Run( - () => clientStatus.DeliverApplicationMessageAsync( - new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build())); - await LongTestDelay(); - await firstMessageOutboundPacketInterceptedTcs.Task; - - var secondMessageTask = Task.Run( - () => clientStatus.DeliverApplicationMessageAsync( - new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build())); - await LongTestDelay(); - - var thirdMessageTask = Task.Run( - () => clientStatus.DeliverApplicationMessageAsync( - new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build())); - await LongTestDelay(); - - Task.WaitAny(firstMessageTask, secondMessageTask, thirdMessageTask); - - // Due to the DropNewMessage strategy the third message delivery will fail. - // As a result, no existing messages in the queue will be dropped (evicted). - Assert.AreEqual(firstMessageTask.Status, TaskStatus.WaitingForActivation); - Assert.AreEqual(secondMessageTask.Status, TaskStatus.WaitingForActivation); - Assert.AreEqual(thirdMessageTask.Status, TaskStatus.Faulted); - Assert.IsTrue(thirdMessageTask.Exception?.InnerException is MqttPendingMessagesOverflowException); - - Assert.IsFalse(firstMessageEvicted); - Assert.IsFalse(secondMessageEvicted); - Assert.IsFalse(thirdMessageEvicted); - } - - [TestMethod] - public async Task Deliver_Application_Message_At_Session_Level_QueueOverflow_DropOldestQueuedMessageStrategy() - { - using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false); + var receiver = await testEnvironment.ConnectClient(); - var server = await testEnvironment.StartServer( - builder => builder - .WithMaxPendingMessagesPerClient(1) - .WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)); + var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); + server.InterceptingOutboundPacketAsync += async args => + { + // - The first message is dequeued normally and calls this delay + // - The second message fills the outbound queue + // - The third message overflows the outbound queue + if (args.Packet is MqttPublishPacket) + { + firstMessageOutboundPacketInterceptedTcs.SetResult(); + await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); + } + }; - var receiver = await testEnvironment.ConnectClient(); + var firstMessageEvicted = false; + var secondMessageEvicted = false; + var thirdMessageEvicted = false; - var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource(); - server.InterceptingOutboundPacketAsync += async args => + server.QueuedApplicationMessageOverwrittenAsync += args => + { + if (args.Packet is not MqttPublishPacket publishPacket) { - // - The first message is dequeued normally and calls this delay - // - The second message fills the outbound queue - // - The third message overflows the outbound queue - if (args.Packet is MqttPublishPacket) - { - firstMessageOutboundPacketInterceptedTcs.SetResult(); - await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken); - } - }; - - var firstMessageEvicted = false; - var secondMessageEvicted = false; - var thirdMessageEvicted = false; - - server.QueuedApplicationMessageOverwrittenAsync += args => + return Task.CompletedTask; + } + + switch (publishPacket.Topic) { - if (args.Packet is not MqttPublishPacket publishPacket) - { - return Task.CompletedTask; - } - - switch (publishPacket.Topic) - { - case "InjectedOne": - firstMessageEvicted = true; - break; - case "InjectedTwo": - secondMessageEvicted = true; - break; - case "InjectedThree": - thirdMessageEvicted = true; - break; - } + case "InjectedOne": + firstMessageEvicted = true; + break; + case "InjectedTwo": + secondMessageEvicted = true; + break; + case "InjectedThree": + thirdMessageEvicted = true; + break; + } + + return Task.CompletedTask; + }; + + var status = await server.GetSessionsAsync(); + var clientStatus = status[0]; + await receiver.SubscribeAsync("#"); + + var firstMessageTask = Task.Run( + () => clientStatus.DeliverApplicationMessageAsync( + new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build())); + await LongTestDelay(); + await firstMessageOutboundPacketInterceptedTcs.Task; + + var secondMessageTask = Task.Run( + () => clientStatus.DeliverApplicationMessageAsync( + new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build())); + await LongTestDelay(); + + var thirdMessageTask = Task.Run( + () => clientStatus.DeliverApplicationMessageAsync( + new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build())); + await LongTestDelay(); + + Task.WaitAny(firstMessageTask, secondMessageTask, thirdMessageTask); + + // Due to the DropOldestQueuedMessage strategy, the second message delivery will fail + // to make room for the third one. + Assert.AreEqual(firstMessageTask.Status, TaskStatus.WaitingForActivation); + Assert.AreEqual(secondMessageTask.Status, TaskStatus.Faulted); + Assert.IsTrue(secondMessageTask.Exception?.InnerException is MqttPendingMessagesOverflowException); + Assert.AreEqual(thirdMessageTask.Status, TaskStatus.WaitingForActivation); + + Assert.IsFalse(firstMessageEvicted); + Assert.IsTrue(secondMessageEvicted); + Assert.IsFalse(thirdMessageEvicted); + } - return Task.CompletedTask; - }; - - var status = await server.GetSessionsAsync(); - var clientStatus = status[0]; - await receiver.SubscribeAsync("#"); - - var firstMessageTask = Task.Run( - () => clientStatus.DeliverApplicationMessageAsync( - new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build())); - await LongTestDelay(); - await firstMessageOutboundPacketInterceptedTcs.Task; - - var secondMessageTask = Task.Run( - () => clientStatus.DeliverApplicationMessageAsync( - new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build())); - await LongTestDelay(); - - var thirdMessageTask = Task.Run( - () => clientStatus.DeliverApplicationMessageAsync( - new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build())); - await LongTestDelay(); - - Task.WaitAny(firstMessageTask, secondMessageTask, thirdMessageTask); - - // Due to the DropOldestQueuedMessage strategy, the second message delivery will fail - // to make room for the third one. - Assert.AreEqual(firstMessageTask.Status, TaskStatus.WaitingForActivation); - Assert.AreEqual(secondMessageTask.Status, TaskStatus.Faulted); - Assert.IsTrue(secondMessageTask.Exception?.InnerException is MqttPendingMessagesOverflowException); - Assert.AreEqual(thirdMessageTask.Status, TaskStatus.WaitingForActivation); - - Assert.IsFalse(firstMessageEvicted); - Assert.IsTrue(secondMessageEvicted); - Assert.IsFalse(thirdMessageEvicted); - } - - [TestMethod] - public async Task Inject_ApplicationMessage_At_Server_Level() - { - using var testEnvironment = CreateTestEnvironment(); + [TestMethod] + public async Task Inject_ApplicationMessage_At_Server_Level() + { + using var testEnvironment = CreateTestEnvironment(); - var server = await testEnvironment.StartServer(); + var server = await testEnvironment.StartServer(); - var receiver = await testEnvironment.ConnectClient(); + var receiver = await testEnvironment.ConnectClient(); - var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver); + var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver); - await receiver.SubscribeAsync("#"); + await receiver.SubscribeAsync("#"); - var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(); + var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(); - await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage)); + await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage)); - await LongTestDelay(); + await LongTestDelay(); - Assert.AreEqual(1, messageReceivedHandler.ReceivedEventArgs.Count); - Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic); - } + Assert.AreEqual(1, messageReceivedHandler.ReceivedEventArgs.Count); + Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic); + } - [TestMethod] - public async Task Intercept_Injected_Application_Message() - { - using var testEnvironment = CreateTestEnvironment(); + [TestMethod] + public async Task Intercept_Injected_Application_Message() + { + using var testEnvironment = CreateTestEnvironment(); - var server = await testEnvironment.StartServer(); + var server = await testEnvironment.StartServer(); - MqttApplicationMessage interceptedMessage = null; - server.InterceptingPublishAsync += eventArgs => - { - interceptedMessage = eventArgs.ApplicationMessage; - return CompletedTask.Instance; - }; + MqttApplicationMessage interceptedMessage = null; + server.InterceptingPublishAsync += eventArgs => + { + interceptedMessage = eventArgs.ApplicationMessage; + return CompletedTask.Instance; + }; - var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(); - await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage)); + var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(); + await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage)); - await LongTestDelay(); + await LongTestDelay(); - Assert.IsNotNull(interceptedMessage); - } + Assert.IsNotNull(interceptedMessage); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Keep_Alive_Tests.cs b/Source/MQTTnet.Tests/Server/Keep_Alive_Tests.cs index 8bcd8b0a8..070a396fb 100644 --- a/Source/MQTTnet.Tests/Server/Keep_Alive_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Keep_Alive_Tests.cs @@ -10,59 +10,57 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class KeepAlive_Tests : BaseTestClass { - [TestClass] - public sealed class KeepAlive_Tests : BaseTestClass + [TestMethod] + public async Task Disconnect_Client_DueTo_KeepAlive() { - [TestMethod] - public async Task Disconnect_Client_DueTo_KeepAlive() + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + var client = await testEnvironment.ConnectLowLevelClient(o => o + .WithTimeout(TimeSpan.FromSeconds(1)) + .WithTimeout(TimeSpan.Zero) + .WithProtocolVersion(MqttProtocolVersion.V500)).ConfigureAwait(false); + + await client.SendAsync(new MqttConnectPacket { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + CleanSession = true, + ClientId = "Disconnect_Client_DueTo_KeepAlive", + KeepAlivePeriod = 1 + }, CancellationToken.None).ConfigureAwait(false); - var client = await testEnvironment.ConnectLowLevelClient(o => o - .WithTimeout(TimeSpan.FromSeconds(1)) - .WithTimeout(TimeSpan.Zero) - .WithProtocolVersion(MqttProtocolVersion.V500)).ConfigureAwait(false); + var responsePacket = await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); + Assert.IsTrue(responsePacket is MqttConnAckPacket); - await client.SendAsync(new MqttConnectPacket - { - CleanSession = true, - ClientId = "Disconnect_Client_DueTo_KeepAlive", - KeepAlivePeriod = 1 - }, CancellationToken.None).ConfigureAwait(false); + for (var i = 0; i < 6; i++) + { + await Task.Delay(500); - var responsePacket = await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); - Assert.IsTrue(responsePacket is MqttConnAckPacket); + await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None); + responsePacket = await client.ReceiveAsync(CancellationToken.None); + Assert.IsTrue(responsePacket is MqttPingRespPacket); + } - for (var i = 0; i < 6; i++) - { - await Task.Delay(500); - - await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None); - responsePacket = await client.ReceiveAsync(CancellationToken.None); - Assert.IsTrue(responsePacket is MqttPingRespPacket); - } - - // If we reach this point everything works as expected (server did not close the connection - // due to proper ping messages. - // Now we will wait 1.1 seconds because the server MUST wait 1.5 seconds in total (See spec). + // If we reach this point everything works as expected (server did not close the connection + // due to proper ping messages. + // Now we will wait 1.1 seconds because the server MUST wait 1.5 seconds in total (See spec). - await Task.Delay(1100); - await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None); - responsePacket = await client.ReceiveAsync(CancellationToken.None); - Assert.IsTrue(responsePacket is MqttPingRespPacket); + await Task.Delay(1100); + await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None); + responsePacket = await client.ReceiveAsync(CancellationToken.None); + Assert.IsTrue(responsePacket is MqttPingRespPacket); - // Now we will wait longer than 1.5 so that the server will close the connection. - responsePacket = await client.ReceiveAsync(CancellationToken.None); + // Now we will wait longer than 1.5 so that the server will close the connection. + responsePacket = await client.ReceiveAsync(CancellationToken.None); - var disconnectPacket = responsePacket as MqttDisconnectPacket; + var disconnectPacket = responsePacket as MqttDisconnectPacket; - Assert.IsTrue(disconnectPacket != null); - Assert.AreEqual(disconnectPacket.ReasonCode, MqttDisconnectReasonCode.KeepAliveTimeout); - } - } + Assert.IsTrue(disconnectPacket != null); + Assert.AreEqual(disconnectPacket.ReasonCode, MqttDisconnectReasonCode.KeepAliveTimeout); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Load_Tests.cs b/Source/MQTTnet.Tests/Server/Load_Tests.cs index 466e1cfa0..64e1b6852 100644 --- a/Source/MQTTnet.Tests/Server/Load_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Load_Tests.cs @@ -6,166 +6,154 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +#if DEBUG +[TestClass] +#endif +public sealed class Load_Tests : BaseTestClass { - [TestClass] - public sealed class Load_Tests : BaseTestClass + [TestMethod] + public async Task Handle_100_000_Messages_In_Low_Level_Client() { - [TestMethod] - public async Task Handle_100_000_Messages_In_Receiving_Client() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); + + var receivedMessages = 0; - var receivedMessages = 0; + server.InterceptingPublishAsync += _ => + { + Interlocked.Increment(ref receivedMessages); + return CompletedTask.Instance; + }; - using (var receiverClient = await testEnvironment.ConnectClient()) + for (var i = 0; i < 100; i++) + { + _ = Task.Factory.StartNew( + async () => { - receiverClient.ApplicationMessageReceivedAsync += e => + try { - Interlocked.Increment(ref receivedMessages); - return CompletedTask.Instance; - }; + using var client = await testEnvironment.ConnectLowLevelClient(); - await receiverClient.SubscribeAsync("t/+"); + await client.SendAsync( + new MqttConnectPacket + { + ClientId = "Handle_100_000_Messages_In_Low_Level_Client_" + Guid.NewGuid() + }, + CancellationToken.None); + var packet = await client.ReceiveAsync(CancellationToken.None); - for (var i = 0; i < 100; i++) - { - _ = Task.Run( - async () => - { - using (var client = await testEnvironment.ConnectClient()) - { - var applicationMessageBuilder = new MqttApplicationMessageBuilder(); - - for (var j = 0; j < 1000; j++) - { - var message = applicationMessageBuilder.WithTopic("t/" + j) - .Build(); - - await client.PublishAsync(message) - .ConfigureAwait(false); - } - - await client.DisconnectAsync(); - } - }); - } + var connAckPacket = packet as MqttConnAckPacket; - SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(120)); + Assert.IsTrue(connAckPacket != null); + Assert.AreEqual(MqttConnectReasonCode.Success, connAckPacket.ReasonCode); - Assert.AreEqual(100000, receivedMessages); - } - } - } + var publishPacket = new MqttPublishPacket(); - [TestMethod] - public async Task Handle_100_000_Messages_In_Low_Level_Client() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + for (var j = 0; j < 1000; j++) + { + publishPacket.Topic = j.ToString(); - var receivedMessages = 0; + await client.SendAsync(publishPacket, CancellationToken.None).ConfigureAwait(false); + } - server.InterceptingPublishAsync += e => - { - Interlocked.Increment(ref receivedMessages); - return CompletedTask.Instance; - }; + await client.DisconnectAsync(CancellationToken.None); + } + catch (Exception exception) + { + Console.WriteLine(exception); + } + }, + TaskCreationOptions.LongRunning); + } - for (var i = 0; i < 100; i++) - { - _ = Task.Run( - async () => - { - try - { - using (var client = await testEnvironment.ConnectLowLevelClient()) - { - await client.SendAsync( - new MqttConnectPacket - { - ClientId = "Handle_100_000_Messages_In_Low_Level_Client_" + Guid.NewGuid() - }, CancellationToken.None); + var result = SpinWait.SpinUntil(() => receivedMessages >= 100000, TimeSpan.FromMinutes(5)); - var packet = await client.ReceiveAsync(CancellationToken.None); + Assert.IsTrue(result); + } - var connAckPacket = packet as MqttConnAckPacket; + [TestMethod] + public async Task Handle_100_000_Messages_In_Receiving_Client() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - Assert.IsTrue(connAckPacket != null); - Assert.AreEqual(MqttConnectReasonCode.Success, connAckPacket.ReasonCode); + var receivedMessages = 0; - var publishPacket = new MqttPublishPacket(); + using var receiverClient = await testEnvironment.ConnectClient(); - for (var j = 0; j < 1000; j++) - { - publishPacket.Topic = j.ToString(); + receiverClient.ApplicationMessageReceivedAsync += _ => + { + Interlocked.Increment(ref receivedMessages); + return CompletedTask.Instance; + }; - await client.SendAsync(publishPacket, CancellationToken.None) - .ConfigureAwait(false); - } + await receiverClient.SubscribeAsync("t/+"); - await client.DisconnectAsync(CancellationToken.None); - } - } - catch (Exception exception) - { - Console.WriteLine(exception); - } - }); - } + for (var i = 0; i < 100; i++) + { + _ = Task.Factory.StartNew( + async () => + { + using var client = await testEnvironment.ConnectClient(); + var applicationMessageBuilder = testEnvironment.ClientFactory.CreateApplicationMessageBuilder(); - SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(10)); + for (var j = 0; j < 1000; j++) + { + var message = applicationMessageBuilder.WithTopic("t/" + j).Build(); - Assert.AreEqual(100000, receivedMessages); - } + await client.PublishAsync(message).ConfigureAwait(false); + } + + await client.DisconnectAsync(); + }, + TaskCreationOptions.LongRunning); } - [TestMethod] - public async Task Handle_100_000_Messages_In_Server() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + var result = SpinWait.SpinUntil(() => receivedMessages >= 100000, TimeSpan.FromMinutes(5)); - var receivedMessages = 0; + Assert.IsTrue(result); + } - server.InterceptingPublishAsync += e => - { - Interlocked.Increment(ref receivedMessages); - return CompletedTask.Instance; - }; + [TestMethod] + public async Task Handle_100_000_Messages_In_Server() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - for (var i = 0; i < 100; i++) - { - _ = Task.Run( - async () => - { - using (var client = await testEnvironment.ConnectClient()) - { - var applicationMessageBuilder = new MqttApplicationMessageBuilder(); + var receivedMessages = 0; - for (var j = 0; j < 1000; j++) - { - var message = applicationMessageBuilder.WithTopic(j.ToString()) - .Build(); + server.InterceptingPublishAsync += _ => + { + Interlocked.Increment(ref receivedMessages); + return CompletedTask.Instance; + }; - await client.PublishAsync(message) - .ConfigureAwait(false); - } + for (var i = 0; i < 100; i++) + { + _ = Task.Factory.StartNew( + async () => + { + using var client = await testEnvironment.ConnectClient(); + var applicationMessageBuilder = new MqttApplicationMessageBuilder(); - await client.DisconnectAsync(); - } - }); - } + for (var j = 0; j < 1000; j++) + { + var message = applicationMessageBuilder.WithTopic(j.ToString()).Build(); - SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(10)); + await client.PublishAsync(message).ConfigureAwait(false); + } - Assert.AreEqual(100000, receivedMessages); - } + await client.DisconnectAsync(); + }, + TaskCreationOptions.LongRunning); } + + var result = SpinWait.SpinUntil(() => receivedMessages >= 100000, TimeSpan.FromMinutes(5)); + + Assert.IsTrue(result); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs b/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs index 227a664e8..ca6f3d6da 100644 --- a/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs +++ b/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs @@ -1,26 +1,22 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using MQTTnet.Server.Internal; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttRetainedMessageManager_Tests { - [TestClass] - public sealed class MqttRetainedMessageManager_Tests + [TestMethod] + public async Task MqttRetainedMessageManager_GetUndefinedTopic() { - [TestMethod] - public async Task MqttRetainedMessageManager_GetUndefinedTopic() - { - var logger = new Mockups.TestLogger(); - var eventContainer = new MqttServerEventContainer(); - var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger); - var task = retainedMessagesManager.GetMessage("undefined"); - Assert.IsNotNull(task, "Task should not be null"); - var result = await task; - Assert.IsNull(result, "Null result expected"); - } + var logger = new Mockups.TestLogger(); + var eventContainer = new MqttServerEventContainer(); + var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger); + var task = retainedMessagesManager.GetMessage("undefined"); + Assert.IsNotNull(task, "Task should not be null"); + var result = await task; + Assert.IsNull(result, "Null result expected"); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs b/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs index 13a8cd9db..e188ea391 100644 --- a/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs +++ b/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Concurrent; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,227 +12,218 @@ using MQTTnet.Server.Internal; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttSubscriptionsManager_Tests : BaseTestClass { - [TestClass] - public sealed class MqttSubscriptionsManager_Tests : BaseTestClass - { - MqttClientSubscriptionsManager _subscriptionsManager; + MqttClientSubscriptionsManager _subscriptionsManager; - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() + { + var sp = new MqttSubscribePacket { - var sp = new MqttSubscribePacket - { - TopicFilters = new List - { - new MqttTopicFilterBuilder().WithTopic("A/B/C").Build() - } - }; + TopicFilters = [new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()] + }; - await _subscriptionsManager.Subscribe(sp, CancellationToken.None); + await _subscriptionsManager.Subscribe(sp, CancellationToken.None); - Assert.IsTrue(CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); + Assert.IsTrue(CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); - var up = new MqttUnsubscribePacket(); - up.TopicFilters.Add("A/B/C"); - await _subscriptionsManager.Unsubscribe(up, CancellationToken.None); + var up = new MqttUnsubscribePacket(); + up.TopicFilters.Add("A/B/C"); + await _subscriptionsManager.Unsubscribe(up, CancellationToken.None); - Assert.IsFalse(CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); - } + Assert.IsFalse(CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() + { + var sp = new MqttSubscribePacket { - var sp = new MqttSubscribePacket - { - TopicFilters = new List - { - new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce } - } - }; - - await _subscriptionsManager.Subscribe(sp, CancellationToken.None); - - var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, ""); - Assert.IsTrue(result.IsSubscribed); - Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); - } - - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeSingleNoSuccess() + TopicFilters = + [ + new() { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce } + ] + }; + + await _subscriptionsManager.Subscribe(sp, CancellationToken.None); + + var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, ""); + Assert.IsTrue(result.IsSubscribed); + Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); + } + + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeSingleNoSuccess() + { + var sp = new MqttSubscribePacket { - var sp = new MqttSubscribePacket - { - TopicFilters = new List - { - new MqttTopicFilterBuilder().WithTopic("A/B/C").Build() - } - }; + TopicFilters = [new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()] + }; - await _subscriptionsManager.Subscribe(sp, CancellationToken.None); + await _subscriptionsManager.Subscribe(sp, CancellationToken.None); - Assert.IsFalse(CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); - } + Assert.IsFalse(CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeSingleSuccess() + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeSingleSuccess() + { + var sp = new MqttSubscribePacket { - var sp = new MqttSubscribePacket - { - TopicFilters = new List - { - new MqttTopicFilterBuilder().WithTopic("A/B/C").Build() - } - }; + TopicFilters = [new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()] + }; - await _subscriptionsManager.Subscribe(sp, CancellationToken.None); + await _subscriptionsManager.Subscribe(sp, CancellationToken.None); - var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, ""); + var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, ""); - Assert.IsTrue(result.IsSubscribed); - Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); - } + Assert.IsTrue(result.IsSubscribed); + Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeTwoTimesSuccess() + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeTwoTimesSuccess() + { + var sp = new MqttSubscribePacket { - var sp = new MqttSubscribePacket - { - TopicFilters = new List - { - new MqttTopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }, - new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce } - } - }; + TopicFilters = + [ + new MqttTopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }, + new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce } + ] + }; - await _subscriptionsManager.Subscribe(sp, CancellationToken.None); + await _subscriptionsManager.Subscribe(sp, CancellationToken.None); - var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, ""); + var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, ""); - Assert.IsTrue(result.IsSubscribed); - Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); - } + Assert.IsTrue(result.IsSubscribed); + Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeWildcard1() - { - await SubscribeToTopic("house/+/room"); - await SubscribeToTopic("house/+/room/+"); + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeWildcard1() + { + await SubscribeToTopic("house/+/room"); + await SubscribeToTopic("house/+/room/+"); - CheckIsSubscribed("house/1/room"); - CheckIsSubscribed("house/1/room/bed"); - CheckIsSubscribed("house/1/room/chair"); - CheckIsSubscribed("house/2/room/bed"); - CheckIsSubscribed("house/2/room/chair"); + CheckIsSubscribed("house/1/room"); + CheckIsSubscribed("house/1/room/bed"); + CheckIsSubscribed("house/1/room/chair"); + CheckIsSubscribed("house/2/room/bed"); + CheckIsSubscribed("house/2/room/chair"); - CheckIsNotSubscribed("house/1/room/bed/cover"); - CheckIsNotSubscribed("house/1/study/bed"); - } + CheckIsNotSubscribed("house/1/room/bed/cover"); + CheckIsNotSubscribed("house/1/study/bed"); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeWildcard2() - { - await SubscribeToTopic("house/+/room"); - await SubscribeToTopic("house/+/room/#"); + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeWildcard2() + { + await SubscribeToTopic("house/+/room"); + await SubscribeToTopic("house/+/room/#"); - CheckIsSubscribed("house/1/room"); - CheckIsSubscribed("house/1/room/bed"); - CheckIsSubscribed("house/2/room"); - CheckIsSubscribed("house/2/room/bed"); - CheckIsSubscribed("house/2/room/bed/cover"); + CheckIsSubscribed("house/1/room"); + CheckIsSubscribed("house/1/room/bed"); + CheckIsSubscribed("house/2/room"); + CheckIsSubscribed("house/2/room/bed"); + CheckIsSubscribed("house/2/room/bed/cover"); - CheckIsNotSubscribed("house/1/level"); - CheckIsNotSubscribed("house/1/level/door"); - } + CheckIsNotSubscribed("house/1/level"); + CheckIsNotSubscribed("house/1/level/door"); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeWildcard3() - { - await SubscribeToTopic("house/1/room"); - await SubscribeToTopic("house/1/room/+"); + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeWildcard3() + { + await SubscribeToTopic("house/1/room"); + await SubscribeToTopic("house/1/room/+"); - CheckIsSubscribed("house/1/room"); - CheckIsSubscribed("house/1/room/bed"); - CheckIsSubscribed("house/1/room/chair"); + CheckIsSubscribed("house/1/room"); + CheckIsSubscribed("house/1/room/bed"); + CheckIsSubscribed("house/1/room/chair"); - CheckIsNotSubscribed("house/2/room"); - CheckIsNotSubscribed("house/2/room/bed/cover"); - } + CheckIsNotSubscribed("house/2/room"); + CheckIsNotSubscribed("house/2/room/bed/cover"); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeWildcard4() - { - await SubscribeToTopic("house/1/+/+"); + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeWildcard4() + { + await SubscribeToTopic("house/1/+/+"); - CheckIsSubscribed("house/1/room/bed"); - CheckIsSubscribed("house/1/room/chair"); + CheckIsSubscribed("house/1/room/bed"); + CheckIsSubscribed("house/1/room/chair"); - CheckIsNotSubscribed("house/1/room"); - CheckIsNotSubscribed("house/1/room/bed/cover"); - } + CheckIsNotSubscribed("house/1/room"); + CheckIsNotSubscribed("house/1/room/bed/cover"); + } - [TestMethod] - public async Task MqttSubscriptionsManager_SubscribeWildcard5() - { - await SubscribeToTopic("house/1/+/#"); + [TestMethod] + public async Task MqttSubscriptionsManager_SubscribeWildcard5() + { + await SubscribeToTopic("house/1/+/#"); - CheckIsSubscribed("house/1/room/bed"); - CheckIsSubscribed("house/1/room/chair"); - CheckIsSubscribed("house/1/room/chair/leg"); - CheckIsSubscribed("house/1/level/window"); - CheckIsSubscribed("house/1/level/door"); + CheckIsSubscribed("house/1/room/bed"); + CheckIsSubscribed("house/1/room/chair"); + CheckIsSubscribed("house/1/room/chair/leg"); + CheckIsSubscribed("house/1/level/window"); + CheckIsSubscribed("house/1/level/door"); - CheckIsNotSubscribed("house/2/room/bed"); - } + CheckIsNotSubscribed("house/2/room/bed"); + } - async Task SubscribeToTopic(string topic) - { - var sp = new MqttSubscribePacket - { - TopicFilters = new List - { - new MqttTopicFilter { Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }, - } - }; - - await _subscriptionsManager.Subscribe(sp, CancellationToken.None); - } - - void CheckIsSubscribed(string topic) + async Task SubscribeToTopic(string topic) + { + var sp = new MqttSubscribePacket { - var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, ""); - Assert.IsTrue(result.IsSubscribed); - Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); - } + TopicFilters = + [ + new MqttTopicFilter { Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce } + ] + }; - void CheckIsNotSubscribed(string topic) - { - var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, ""); - Assert.IsFalse(result.IsSubscribed); - } + await _subscriptionsManager.Subscribe(sp, CancellationToken.None).ConfigureAwait(false); + } - [TestInitialize] - public void TestInitialize() - { - var logger = new TestLogger(); - var options = new MqttServerOptions(); - var retainedMessagesManager = new MqttRetainedMessagesManager(new MqttServerEventContainer(), logger); - var eventContainer = new MqttServerEventContainer(); - var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger); + void CheckIsSubscribed(string topic) + { + var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, ""); + Assert.IsTrue(result.IsSubscribed); + Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); + } - var session = new MqttSession(new MqttConnectPacket - { - ClientId = "" - }, new ConcurrentDictionary(), options, eventContainer, retainedMessagesManager, clientSessionManager); + void CheckIsNotSubscribed(string topic) + { + var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, ""); + Assert.IsFalse(result.IsSubscribed); + } - _subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager); - } + [TestInitialize] + public void TestInitialize() + { + var logger = new TestLogger(); + var options = new MqttServerOptions(); + var retainedMessagesManager = new MqttRetainedMessagesManager(new MqttServerEventContainer(), logger); + var eventContainer = new MqttServerEventContainer(); + var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger); - CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) + var session = new MqttSession(new MqttConnectPacket { - MqttTopicHash.Calculate(topic, out var topicHash, out _, out _); - return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId); - } + ClientId = "" + }, new ConcurrentDictionary(), options, eventContainer, retainedMessagesManager, clientSessionManager); + + _subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager); + } + + CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) + { + MqttTopicHash.Calculate(topic, out var topicHash, out _, out _); + return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/No_Local_Tests.cs b/Source/MQTTnet.Tests/Server/No_Local_Tests.cs index 5a8627d2f..7eaca6070 100644 --- a/Source/MQTTnet.Tests/Server/No_Local_Tests.cs +++ b/Source/MQTTnet.Tests/Server/No_Local_Tests.cs @@ -6,45 +6,43 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class No_Local_Tests : BaseTestClass { - [TestClass] - public sealed class No_Local_Tests : BaseTestClass + [TestMethod] + public Task Subscribe_With_No_Local() + { + return ExecuteTest(true, 0); + } + + [TestMethod] + public Task Subscribe_Without_No_Local() { - [TestMethod] - public Task Subscribe_With_No_Local() - { - return ExecuteTest(true, 0); - } - - [TestMethod] - public Task Subscribe_Without_No_Local() - { - return ExecuteTest(false, 1); - } - - async Task ExecuteTest( - bool noLocal, - int expectedCountAfterPublish) - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); - - var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); - var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithNoLocal(noLocal).Build(); - await client1.SubscribeAsync(topicFilter); - await LongTestDelay(); - - applicationMessageHandler.AssertReceivedCountEquals(0); - - // The client will publish a message where it is itself subscribing to. - await client1.PublishStringAsync("Topic", "Payload", retain: true); - await LongTestDelay(); - - applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterPublish); - } - } + return ExecuteTest(false, 1); + } + + async Task ExecuteTest( + bool noLocal, + int expectedCountAfterPublish) + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); + + var client1 = await testEnvironment.ConnectClient(); + var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithNoLocal(noLocal).Build(); + await client1.SubscribeAsync(topicFilter); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(0); + + // The client will publish a message where it is itself subscribing to. + await client1.PublishStringAsync("Topic", "Payload", retain: true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterPublish); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Publishing_Tests.cs b/Source/MQTTnet.Tests/Server/Publishing_Tests.cs index e829e2573..b10789eb4 100644 --- a/Source/MQTTnet.Tests/Server/Publishing_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Publishing_Tests.cs @@ -10,138 +10,128 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Publishing_Tests : BaseTestClass { - [TestClass] - public sealed class Publishing_Tests : BaseTestClass + [TestMethod] + [ExpectedException(typeof(MqttClientDisconnectedException))] + public async Task Disconnect_While_Publishing() { - [TestMethod] - [ExpectedException(typeof(MqttClientDisconnectedException))] - public async Task Disconnect_While_Publishing() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - // The client will be disconnect directly after subscribing! - server.InterceptingPublishAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection); + // The client will be disconnected directly after subscribing! + server.InterceptingPublishAsync += ev => server.DisconnectClientAsync(ev.ClientId); - var client = await testEnvironment.ConnectClient(); - await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - } - } + var client = await testEnvironment.ConnectClient(); + await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); + } - [TestMethod] - public async Task Return_NoMatchingSubscribers_When_Not_Subscribed() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Return_NoMatchingSubscribers_When_Not_Subscribed() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var sender = await testEnvironment.ConnectClient(); - var receiver = await testEnvironment.ConnectClient(); + var sender = await testEnvironment.ConnectClient(); + var receiver = await testEnvironment.ConnectClient(); - await receiver.SubscribeAsync("A"); + await receiver.SubscribeAsync("A"); - // AtLeastOnce is required to get an ACK packet from the server. - var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); + // AtLeastOnce is required to get an ACK packet from the server. + var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); - Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode); + Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode); - Assert.AreEqual(true, publishResult.IsSuccess); - } - } + Assert.AreEqual(true, publishResult.IsSuccess); + } - [TestMethod] - public async Task Return_Success_When_Subscribed() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Return_Success_When_Subscribed() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var sender = await testEnvironment.ConnectClient(); - var receiver = await testEnvironment.ConnectClient(); + var sender = await testEnvironment.ConnectClient(); + var receiver = await testEnvironment.ConnectClient(); - await receiver.SubscribeAsync("A"); + await receiver.SubscribeAsync("A"); - // AtLeastOnce is required to get an ACK packet from the server. - var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); + // AtLeastOnce is required to get an ACK packet from the server. + var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); - Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode); + Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode); - Assert.AreEqual(true, publishResult.IsSuccess); - } - } + Assert.AreEqual(true, publishResult.IsSuccess); + } - [TestMethod] - public async Task Intercept_Client_Enqueue() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + [TestMethod] + public async Task Intercept_Client_Enqueue() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - var sender = await testEnvironment.ConnectClient(); + var sender = await testEnvironment.ConnectClient(); - var receiver = await testEnvironment.ConnectClient(); - await receiver.SubscribeAsync("A"); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + var receiver = await testEnvironment.ConnectClient(); + await receiver.SubscribeAsync("A"); + var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); - await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); + await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); - await LongTestDelay(); + await LongTestDelay(); - receivedMessages.AssertReceivedCountEquals(1); + receivedMessages.AssertReceivedCountEquals(1); - server.InterceptingClientEnqueueAsync += e => - { - e.AcceptEnqueue = false; - return CompletedTask.Instance; - }; + server.InterceptingClientEnqueueAsync += e => + { + e.AcceptEnqueue = false; + return CompletedTask.Instance; + }; - await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); + await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); - await LongTestDelay(); + await LongTestDelay(); - // Do not increase because the internal enqueue to the target client is not accepted! - receivedMessages.AssertReceivedCountEquals(1); - } - } + // Do not increase because the internal enqueue to the target client is not accepted! + receivedMessages.AssertReceivedCountEquals(1); + } - [TestMethod] - public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + [TestMethod] + public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - var sender = await testEnvironment.ConnectClient(); + var sender = await testEnvironment.ConnectClient(); - var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One")); - await receiverOne.SubscribeAsync("A"); - var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne); + var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One")); + await receiverOne.SubscribeAsync("A"); + var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne); - var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two")); - await receiverTwo.SubscribeAsync("A"); - var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo); + var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two")); + await receiverTwo.SubscribeAsync("A"); + var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo); - var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three")); - await receiverThree.SubscribeAsync("A"); - var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree); + var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three")); + await receiverThree.SubscribeAsync("A"); + var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree); - server.InterceptingClientEnqueueAsync += e => - { - if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false; - return CompletedTask.Instance; - }; + server.InterceptingClientEnqueueAsync += e => + { + if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false; + return CompletedTask.Instance; + }; - await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); + await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); - await LongTestDelay(); + await LongTestDelay(); - receivedMessagesOne.AssertReceivedCountEquals(1); - receivedMessagesTwo.AssertReceivedCountEquals(0); - receivedMessagesThree.AssertReceivedCountEquals(1); - } - } + receivedMessagesOne.AssertReceivedCountEquals(1); + receivedMessagesTwo.AssertReceivedCountEquals(0); + receivedMessagesThree.AssertReceivedCountEquals(1); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/QoS_Tests.cs b/Source/MQTTnet.Tests/Server/QoS_Tests.cs index b48a1a99c..59c4a28cb 100644 --- a/Source/MQTTnet.Tests/Server/QoS_Tests.cs +++ b/Source/MQTTnet.Tests/Server/QoS_Tests.cs @@ -11,157 +11,150 @@ namespace MQTTnet.Tests.Server; +// ReSharper disable InconsistentNaming [TestClass] public sealed class QoS_Tests : BaseTestClass { [TestMethod] public async Task Fire_Event_On_Client_Acknowledges_QoS_0() { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - ClientAcknowledgedPublishPacketEventArgs eventArgs = null; - server.ClientAcknowledgedPublishPacketAsync += args => - { - eventArgs = args; - return CompletedTask.Instance; - }; + ClientAcknowledgedPublishPacketEventArgs eventArgs = null; + server.ClientAcknowledgedPublishPacketAsync += args => + { + eventArgs = args; + return CompletedTask.Instance; + }; - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("A"); + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("A"); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("A"); + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("A"); - await LongTestDelay(); + await LongTestDelay(); - // Must be null because no event should be fired for QoS 0. - Assert.IsNull(eventArgs); - } + // Must be null because no event should be fired for QoS 0. + Assert.IsNull(eventArgs); } [TestMethod] public async Task Fire_Event_On_Client_Acknowledges_QoS_1() { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - ClientAcknowledgedPublishPacketEventArgs eventArgs = null; - server.ClientAcknowledgedPublishPacketAsync += args => - { - eventArgs = args; - return CompletedTask.Instance; - }; + ClientAcknowledgedPublishPacketEventArgs eventArgs = null; + server.ClientAcknowledgedPublishPacketAsync += args => + { + eventArgs = args; + return CompletedTask.Instance; + }; - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtLeastOnce); + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtLeastOnce); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - await LongTestDelay(); + await LongTestDelay(); - Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs.PublishPacket); - Assert.IsNotNull(eventArgs.AcknowledgePacket); - Assert.IsTrue(eventArgs.IsCompleted); + Assert.IsNotNull(eventArgs); + Assert.IsNotNull(eventArgs.PublishPacket); + Assert.IsNotNull(eventArgs.AcknowledgePacket); + Assert.IsTrue(eventArgs.IsCompleted); - Assert.AreEqual("A", eventArgs.PublishPacket.Topic); - } + Assert.AreEqual("A", eventArgs.PublishPacket.Topic); } [TestMethod] public async Task Fire_Event_On_Client_Acknowledges_QoS_2() { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - var eventArgs = new List(); - server.ClientAcknowledgedPublishPacketAsync += args => + var eventArgs = new List(); + server.ClientAcknowledgedPublishPacketAsync += args => + { + lock (eventArgs) { - lock (eventArgs) - { - eventArgs.Add(args); - } + eventArgs.Add(args); + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.ExactlyOnce); + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.ExactlyOnce); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce); + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce); - await LongTestDelay(); + await LongTestDelay(); - Assert.AreEqual(1, eventArgs.Count); + Assert.AreEqual(1, eventArgs.Count); - var firstEvent = eventArgs[0]; + var firstEvent = eventArgs[0]; - Assert.IsNotNull(firstEvent); - Assert.IsNotNull(firstEvent.PublishPacket); - Assert.IsNotNull(firstEvent.AcknowledgePacket); - Assert.IsTrue(firstEvent.IsCompleted); + Assert.IsNotNull(firstEvent); + Assert.IsNotNull(firstEvent.PublishPacket); + Assert.IsNotNull(firstEvent.AcknowledgePacket); + Assert.IsTrue(firstEvent.IsCompleted); - Assert.AreEqual("A", firstEvent.PublishPacket.Topic); - } + Assert.AreEqual("A", firstEvent.PublishPacket.Topic); } [TestMethod] public async Task Preserve_Message_Order_For_Queued_Messages() { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); - // Create a session which will contain the messages. - var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false)); - await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce); - dummyClient.Dispose(); + // Create a session which will contain the messages. + var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false)); + await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce); + dummyClient.Dispose(); - await LongTestDelay(); - await LongTestDelay(); + await LongTestDelay(); + await LongTestDelay(); - // Now inject messages which are appended to the queue of the client. - await server.InjectApplicationMessage("T", "0", MqttQualityOfServiceLevel.AtLeastOnce); + // Now inject messages which are appended to the queue of the client. + await server.InjectApplicationMessage("T", "0", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "2", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "1", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "2", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "1", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "4", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "3", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "4", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "3", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "6", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "5", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "6", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "5", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "8", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "7", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "8", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "7", MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "9", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "9", MqttQualityOfServiceLevel.AtLeastOnce); - await LongTestDelay(); + await LongTestDelay(); - // Create a new client for the existing message. - var client = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false)); - var messages = testEnvironment.CreateApplicationMessageHandler(client); + // Create a new client for the existing message. + var client = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false)); + var messages = testEnvironment.CreateApplicationMessageHandler(client); - await LongTestDelay(); + await LongTestDelay(); - var payloadSequence = messages.GeneratePayloadSequence(); - Assert.AreEqual("0214365879", payloadSequence); + var payloadSequence = messages.GeneratePayloadSequence(); + Assert.AreEqual("0214365879", payloadSequence); - // Disconnect and reconnect to make sure that the server will not send the messages twice. - await client.DisconnectAsync(); - await LongTestDelay(); - await client.ReconnectAsync(); - await LongTestDelay(); + // Disconnect and reconnect to make sure that the server will not send the messages twice. + await client.DisconnectAsync(); + await LongTestDelay(); + await client.ReconnectAsync(); + await LongTestDelay(); - payloadSequence = messages.GeneratePayloadSequence(); - Assert.AreEqual("0214365879", payloadSequence); - } + payloadSequence = messages.GeneratePayloadSequence(); + Assert.AreEqual("0214365879", payloadSequence); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs b/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs index 43e706247..ba5416088 100644 --- a/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs @@ -6,42 +6,40 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Retain_As_Published_Tests : BaseTestClass { - [TestClass] - public sealed class Retain_As_Published_Tests : BaseTestClass + [TestMethod] + public Task Subscribe_With_Retain_As_Published() + { + return ExecuteTest(true); + } + + [TestMethod] + public Task Subscribe_Without_Retain_As_Published() + { + return ExecuteTest(false); + } + + async Task ExecuteTest(bool retainAsPublished) { - [TestMethod] - public Task Subscribe_With_Retain_As_Published() - { - return ExecuteTest(true); - } - - [TestMethod] - public Task Subscribe_Without_Retain_As_Published() - { - return ExecuteTest(false); - } - - async Task ExecuteTest(bool retainAsPublished) - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); - - var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); - var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainAsPublished(retainAsPublished).Build(); - await client1.SubscribeAsync(topicFilter); - await LongTestDelay(); - - // The client will publish a message where it is itself subscribing to. - await client1.PublishStringAsync("Topic", "Payload", retain: true); - await LongTestDelay(); - - applicationMessageHandler.AssertReceivedCountEquals(1); - Assert.AreEqual(retainAsPublished, applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.Retain); - } - } + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); + + var client1 = await testEnvironment.ConnectClient(); + var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainAsPublished(retainAsPublished).Build(); + await client1.SubscribeAsync(topicFilter); + await LongTestDelay(); + + // The client will publish a message where it is itself subscribing to. + await client1.PublishStringAsync("Topic", "Payload", retain: true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(1); + Assert.AreEqual(retainAsPublished, applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.Retain); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs b/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs index aa4b7f227..50b625a1f 100644 --- a/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs @@ -7,63 +7,61 @@ using MQTTnet.Formatter; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Retain_Handling_Tests : BaseTestClass { - [TestClass] - public sealed class Retain_Handling_Tests : BaseTestClass + [TestMethod] + public Task Send_At_Subscribe() { - [TestMethod] - public Task Send_At_Subscribe() - { - return ExecuteTest(MqttRetainHandling.SendAtSubscribe, 1, 2, 3); - } + return ExecuteTest(MqttRetainHandling.SendAtSubscribe, 1, 2, 3); + } - [TestMethod] - public Task Do_Not_Send_On_Subscribe() - { - return ExecuteTest(MqttRetainHandling.DoNotSendOnSubscribe, 0, 1, 1); - } + [TestMethod] + public Task Do_Not_Send_On_Subscribe() + { + return ExecuteTest(MqttRetainHandling.DoNotSendOnSubscribe, 0, 1, 1); + } - [TestMethod] - public Task Send_At_Subscribe_If_New_Subscription_Only() - { - return ExecuteTest(MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly, 1, 2, 2); - } + [TestMethod] + public Task Send_At_Subscribe_If_New_Subscription_Only() + { + return ExecuteTest(MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly, 1, 2, 2); + } - async Task ExecuteTest( - MqttRetainHandling retainHandling, - int expectedCountAfterSubscribe, - int expectedCountAfterSecondPublish, - int expectedCountAfterSecondSubscribe) - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + async Task ExecuteTest( + MqttRetainHandling retainHandling, + int expectedCountAfterSubscribe, + int expectedCountAfterSecondPublish, + int expectedCountAfterSecondSubscribe) + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClient(); - await client1.PublishStringAsync("Topic", "Payload", retain: true); + var client1 = await testEnvironment.ConnectClient(); + await client1.PublishStringAsync("Topic", "Payload", retain: true); - await LongTestDelay(); + await LongTestDelay(); - var client2 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client2); + var client2 = await testEnvironment.ConnectClient(); + var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client2); - var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainHandling(retainHandling).Build(); - await client2.SubscribeAsync(topicFilter); - await LongTestDelay(); + var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainHandling(retainHandling).Build(); + await client2.SubscribeAsync(topicFilter); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSubscribe); + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSubscribe); - await client1.PublishStringAsync("Topic", "Payload", retain: true); - await LongTestDelay(); + await client1.PublishStringAsync("Topic", "Payload", retain: true); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondPublish); + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondPublish); - await client2.SubscribeAsync(topicFilter); - await LongTestDelay(); + await client2.SubscribeAsync(topicFilter); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondSubscribe); - } - } + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondSubscribe); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs b/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs index fbbe500d8..562b1134f 100644 --- a/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs @@ -9,263 +9,243 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Retained_Messages_Tests : BaseTestClass { - [TestClass] - public sealed class Retained_Messages_Tests : BaseTestClass + [TestMethod] + public async Task Clear_Retained_Message_With_Empty_Payload() { - [TestMethod] - public async Task Clear_Retained_Message_With_Empty_Payload() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - var c1 = await testEnvironment.ConnectClient(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); + var c1 = await testEnvironment.ConnectClient(); - await c1.DisconnectAsync(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + await c1.DisconnectAsync(); - await Task.Delay(200); - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - await Task.Delay(500); + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - messageHandler.AssertReceivedCountEquals(0); - } - } + await Task.Delay(200); + await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + await Task.Delay(500); - [TestMethod] - public async Task Clear_Retained_Message_With_Null_Payload() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + messageHandler.AssertReceivedCountEquals(0); + } - var c1 = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task Clear_Retained_Message_With_Null_Payload() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build()); + var c1 = await testEnvironment.ConnectClient(); - await c1.DisconnectAsync(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build()); - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + await c1.DisconnectAsync(); - await Task.Delay(200); - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - await Task.Delay(500); + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - messageHandler.AssertReceivedCountEquals(0); - } - } + await Task.Delay(200); + await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + await Task.Delay(500); - [TestMethod] - public async Task Downgrade_QoS_Level() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + messageHandler.AssertReceivedCountEquals(0); + } - var c1 = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task Downgrade_QoS_Level() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - // Add the retained message with QoS 2! - await c1.PublishAsync( - new MqttApplicationMessageBuilder().WithTopic("retained") - .WithPayload(new byte[3]) - .WithRetainFlag() - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) - .Build()); + var c1 = await testEnvironment.ConnectClient(); - // The second client uses QoS 1 so a downgrade is required. - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + // Add the retained message with QoS 2! + await c1.PublishAsync( + new MqttApplicationMessageBuilder().WithTopic("retained") + .WithPayload(new byte[3]) + .WithRetainFlag() + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) + .Build()); - await LongTestDelay(); + // The second client uses QoS 1 so a downgrade is required. + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - messageHandler.AssertReceivedCountEquals(1); + await LongTestDelay(); - Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel); - } - } + messageHandler.AssertReceivedCountEquals(1); - [TestMethod] - public async Task No_Upgrade_QoS_Level() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel); + } - var c1 = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task No_Upgrade_QoS_Level() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - // Add the retained message with QoS 1! - await c1.PublishAsync( - new MqttApplicationMessageBuilder().WithTopic("retained") - .WithPayload(new byte[3]) - .WithRetainFlag() - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .Build()); + var c1 = await testEnvironment.ConnectClient(); - // The second client uses QoS 2 so an upgrade is expected but according to the MQTT spec this is not supported! - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); + // Add the retained message with QoS 1! + await c1.PublishAsync( + new MqttApplicationMessageBuilder().WithTopic("retained") + .WithPayload(new byte[3]) + .WithRetainFlag() + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .Build()); - await LongTestDelay(); + // The second client uses QoS 2 so an upgrade is expected but according to the MQTT spec this is not supported! + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); - messageHandler.AssertReceivedCountEquals(1); + await LongTestDelay(); - Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel); - } - } + messageHandler.AssertReceivedCountEquals(1); - [TestMethod] - public async Task Receive_No_Retained_Message_After_Subscribe() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel); + } - var c1 = await testEnvironment.ConnectClient(); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.DisconnectAsync(); + [TestMethod] + public async Task Receive_No_Retained_Message_After_Subscribe() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained_other").Build()); + var c1 = await testEnvironment.ConnectClient(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.DisconnectAsync(); - await Task.Delay(500); + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained_other").Build()); - messageHandler.AssertReceivedCountEquals(0); - } - } + await Task.Delay(500); - [TestMethod] - public async Task Receive_Retained_Message_After_Subscribe() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + messageHandler.AssertReceivedCountEquals(0); + } - var c1 = await testEnvironment.ConnectClient(); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.DisconnectAsync(); + [TestMethod] + public async Task Receive_Retained_Message_After_Subscribe() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + var c1 = await testEnvironment.ConnectClient(); + await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); + await c1.DisconnectAsync(); - await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build()); + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - await Task.Delay(500); + await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build()); - messageHandler.AssertReceivedCountEquals(1); - Assert.IsTrue(messageHandler.ReceivedEventArgs.First().ApplicationMessage.Retain); - } - } + await Task.Delay(500); - [TestMethod] - public async Task Receive_Retained_Messages_From_Higher_Qos_Level() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - // Upload retained message. - var c1 = await testEnvironment.ConnectClient(); - await c1.PublishAsync( - new MqttApplicationMessageBuilder().WithTopic("retained") - .WithPayload(new byte[1]) - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) - .WithRetainFlag() - .Build()); - - await c1.DisconnectAsync(); - - // Subscribe using a new client. - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - - await Task.Delay(200); - // Using QoS 2 will lead to 1 instead because the publish was made with QoS level 1 (see 3.8.4 SUBSCRIBE Actions)! - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); - await Task.Delay(500); - - messageHandler.AssertReceivedCountEquals(1); - } - } + messageHandler.AssertReceivedCountEquals(1); + Assert.IsTrue(messageHandler.ReceivedEventArgs.First().ApplicationMessage.Retain); + } - [TestMethod] - public async Task Retained_Messages_Flow() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); + [TestMethod] + public async Task Receive_Retained_Messages_From_Higher_Qos_Level() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + // Upload retained message. + var c1 = await testEnvironment.ConnectClient(); + await c1.PublishAsync( + new MqttApplicationMessageBuilder().WithTopic("retained") + .WithPayload(new byte[1]) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithRetainFlag() + .Build()); + + await c1.DisconnectAsync(); + + // Subscribe using a new client. + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + + await Task.Delay(200); + // Using QoS 2 will lead to 1 instead because the publish was made with QoS level 1 (see 3.8.4 SUBSCRIBE Actions)! + await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); + await Task.Delay(500); + + messageHandler.AssertReceivedCountEquals(1); + } - await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClient(); + [TestMethod] + public async Task Retained_Messages_Flow() + { + using var testEnvironment = CreateTestEnvironment(); + var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); - var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + await testEnvironment.StartServer(); + var c1 = await testEnvironment.ConnectClient(); - await c1.PublishAsync(retainedMessage); - await c1.DisconnectAsync(); - await LongTestDelay(); + var c2 = await testEnvironment.ConnectClient(); + var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); - for (var i = 0; i < 5; i++) - { - await c2.UnsubscribeAsync("r"); - await Task.Delay(100); - messageHandler.AssertReceivedCountEquals(i); + await c1.PublishAsync(retainedMessage); + await c1.DisconnectAsync(); + await LongTestDelay(); - await c2.SubscribeAsync("r"); - await Task.Delay(100); - messageHandler.AssertReceivedCountEquals(i + 1); - } + for (var i = 0; i < 5; i++) + { + await c2.UnsubscribeAsync("r"); + await Task.Delay(100); + messageHandler.AssertReceivedCountEquals(i); - await c2.DisconnectAsync(); - } + await c2.SubscribeAsync("r"); + await Task.Delay(100); + messageHandler.AssertReceivedCountEquals(i + 1); } - [TestMethod] - public async Task Server_Reports_Retained_Messages_Supported_V3() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + await c2.DisconnectAsync(); + } - var client = testEnvironment.CreateClient(); + [TestMethod] + public async Task Server_Reports_Retained_Messages_Supported_V3() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var connectResult = await client.ConnectAsync( - testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V311) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .Build()); + var client = testEnvironment.CreateClient(); - Assert.IsTrue(connectResult.RetainAvailable); - } - } + var connectResult = await client.ConnectAsync( + testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V311) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .Build()); - [TestMethod] - public async Task Server_Reports_Retained_Messages_Supported_V5() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); - - var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync( - testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .Build()); - - Assert.IsTrue(connectResult.RetainAvailable); - } - } + Assert.IsTrue(connectResult.RetainAvailable); + } + + [TestMethod] + public async Task Server_Reports_Retained_Messages_Supported_V5() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); + + var client = testEnvironment.CreateClient(); + var connectResult = await client.ConnectAsync( + testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .Build()); + + Assert.IsTrue(connectResult.RetainAvailable); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Security_Tests.cs b/Source/MQTTnet.Tests/Server/Security_Tests.cs index 404314c43..2e7eba299 100644 --- a/Source/MQTTnet.Tests/Server/Security_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Security_Tests.cs @@ -10,161 +10,151 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Security_Tests : BaseTestClass { - [TestClass] - public sealed class Security_Tests : BaseTestClass + [TestMethod] + public async Task Do_Not_Affect_Authorized_Clients() { - [TestMethod] - public async Task Do_Not_Affect_Authorized_Clients() + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; + + await testEnvironment.StartServer(); + + var publishedApplicationMessages = new List(); + testEnvironment.Server.InterceptingPublishAsync += eventArgs => { - using (var testEnvironment = CreateTestEnvironment()) + lock (publishedApplicationMessages) { - testEnvironment.IgnoreClientLogErrors = true; - - await testEnvironment.StartServer(); - - var publishedApplicationMessages = new List(); - testEnvironment.Server.InterceptingPublishAsync += eventArgs => - { - lock (publishedApplicationMessages) - { - publishedApplicationMessages.Add(eventArgs.ApplicationMessage); - } - - return CompletedTask.Instance; - }; - - testEnvironment.Server.ValidatingConnectionAsync += eventArgs => - { - if (eventArgs.UserName == "SECRET") - { - eventArgs.ReasonCode = MqttConnectReasonCode.Success; - } - else - { - eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized; - } - - return CompletedTask.Instance; - }; - - using (var validClient = testEnvironment.CreateClient()) - { - await validClient.ConnectAsync( - testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithTcpServer("localhost", testEnvironment.ServerPort) - .WithCredentials("SECRET") - .WithClientId("CLIENT") - .Build()); - - await validClient.PublishStringAsync("HELLO 1"); - - // The following code tries to connect a new client with the same client ID but invalid - // credentials. This should block the second client but keep the first one up and running. - try - { - using (var invalidClient = testEnvironment.CreateClient()) - { - await invalidClient.ConnectAsync( - testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithTcpServer("localhost", testEnvironment.ServerPort) - .WithCredentials("???") - .WithClientId("CLIENT") - .Build()); - } - } - catch - { - // Ignore errors from the second client. - } - - await LongTestDelay(); - - await validClient.PublishStringAsync("HELLO 2"); - - await LongTestDelay(); - - await validClient.PublishStringAsync("HELLO 3"); - - await LongTestDelay(); - - Assert.AreEqual(3, publishedApplicationMessages.Count); - Assert.AreEqual(1, testEnvironment.Server.GetClientsAsync().GetAwaiter().GetResult().Count); - } + publishedApplicationMessages.Add(eventArgs.ApplicationMessage); } - } - [TestMethod] - public Task Handle_Wrong_Password() + return CompletedTask.Instance; + }; + + testEnvironment.Server.ValidatingConnectionAsync += eventArgs => { - return TestCredentials("UserName", "x"); - } + if (eventArgs.UserName == "SECRET") + { + eventArgs.ReasonCode = MqttConnectReasonCode.Success; + } + else + { + eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized; + } + + return CompletedTask.Instance; + }; - [TestMethod] - public Task Handle_Wrong_UserName() + using var validClient = testEnvironment.CreateClient(); + await validClient.ConnectAsync( + testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithTcpServer("localhost", testEnvironment.ServerPort) + .WithCredentials("SECRET") + .WithClientId("CLIENT") + .Build()); + + await validClient.PublishStringAsync("HELLO 1"); + + // The following code tries to connect a new client with the same client ID but invalid + // credentials. This should block the second client but keep the first one up and running. + try { - return TestCredentials("x", "Password1"); + using var invalidClient = testEnvironment.CreateClient(); + await invalidClient.ConnectAsync( + testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithTcpServer("localhost", testEnvironment.ServerPort) + .WithCredentials("???") + .WithClientId("CLIENT") + .Build()); } - - [TestMethod] - public Task Handle_Wrong_UserName_And_Password() + catch { - return TestCredentials("x", "x"); + // Ignore errors from the second client. } - [TestMethod] - public async Task Use_Username_Null_Password_Empty() - { - string username = null; - var password = string.Empty; + await LongTestDelay(); - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; + await validClient.PublishStringAsync("HELLO 2"); - await testEnvironment.StartServer(); + await LongTestDelay(); - var client = testEnvironment.CreateClient(); + await validClient.PublishStringAsync("HELLO 3"); - var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(username, password).Build(); + await LongTestDelay(); - var ex = await Assert.ThrowsExceptionAsync(async () => await client.ConnectAsync(clientOptions)); - Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException)); - Assert.AreEqual("Error while authenticating. If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].", ex.Message, false); - } - } + Assert.AreEqual(3, publishedApplicationMessages.Count); + Assert.AreEqual(1, testEnvironment.Server.GetClientsAsync().GetAwaiter().GetResult().Count); + } - async Task TestCredentials(string userName, string password) - { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; + [TestMethod] + public Task Handle_Wrong_Password() + { + return TestCredentials("UserName", "x"); + } + + [TestMethod] + public Task Handle_Wrong_UserName() + { + return TestCredentials("x", "Password1"); + } + + [TestMethod] + public Task Handle_Wrong_UserName_And_Password() + { + return TestCredentials("x", "x"); + } + + [TestMethod] + public async Task Use_Username_Null_Password_Empty() + { + string username = null; + var password = string.Empty; + + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; + + await testEnvironment.StartServer(); - var server = await testEnvironment.StartServer(); + var client = testEnvironment.CreateClient(); - server.ValidatingConnectionAsync += e => - { - if (e.UserName != "UserName1") - { - e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; - } + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(username, password).Build(); - if (e.Password != "Password1") - { - e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; - } + var ex = await Assert.ThrowsExceptionAsync(async () => await client.ConnectAsync(clientOptions).ConfigureAwait(false)); + Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException)); + Assert.AreEqual("Error while authenticating. If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].", ex.Message, false); + } - return CompletedTask.Instance; - }; + async Task TestCredentials(string userName, string password) + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; - var client = testEnvironment.CreateClient(); + var server = await testEnvironment.StartServer(); - var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build(); + server.ValidatingConnectionAsync += e => + { + if (e.UserName != "UserName1") + { + e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + } - var response = await client.ConnectAsync(clientOptions); - Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, response.ResultCode); + if (e.Password != "Password1") + { + e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; } - } + + return CompletedTask.Instance; + }; + + var client = testEnvironment.CreateClient(); + + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build(); + + var response = await client.ConnectAsync(clientOptions); + Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, response.ResultCode); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Server_Reference_Tests.cs b/Source/MQTTnet.Tests/Server/Server_Reference_Tests.cs index 2b4f96c1c..295f72864 100644 --- a/Source/MQTTnet.Tests/Server/Server_Reference_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Server_Reference_Tests.cs @@ -10,32 +10,31 @@ namespace MQTTnet.Tests.Server; +// ReSharper disable InconsistentNaming [TestClass] public sealed class Server_Reference_Tests : BaseTestClass { [TestMethod] public async Task Server_Reports_With_Reference_Server() { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; - var server = await testEnvironment.StartServer(); + var server = await testEnvironment.StartServer(); - server.ValidatingConnectionAsync += e => - { - e.ReasonCode = MqttConnectReasonCode.ServerMoved; - e.ServerReference = "new_server"; - return CompletedTask.Instance; - }; + server.ValidatingConnectionAsync += e => + { + e.ReasonCode = MqttConnectReasonCode.ServerMoved; + e.ServerReference = "new_server"; + return CompletedTask.Instance; + }; - var client = testEnvironment.CreateClient(); + var client = testEnvironment.CreateClient(); - var response = await client.ConnectAsync( - new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + var response = await client.ConnectAsync( + new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - Assert.AreEqual(MqttClientConnectResultCode.ServerMoved, response.ResultCode); - Assert.AreEqual("new_server", response.ServerReference); - } + Assert.AreEqual(MqttClientConnectResultCode.ServerMoved, response.ResultCode); + Assert.AreEqual("new_server", response.ServerReference); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Session_Tests.cs b/Source/MQTTnet.Tests/Server/Session_Tests.cs index 91345da56..2467adb56 100644 --- a/Source/MQTTnet.Tests/Server/Session_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Session_Tests.cs @@ -13,377 +13,357 @@ using MQTTnet.Protocol; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Session_Tests : BaseTestClass { - [TestClass] - public sealed class Session_Tests : BaseTestClass + [TestMethod] + public async Task Clean_Session_Persistence() { - [TestMethod] - public async Task Clean_Session_Persistence() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - // Create server with persistent sessions enabled + using var testEnvironment = new TestEnvironment(TestContext); + // Create server with persistent sessions enabled - await testEnvironment.StartServer(o => o.WithPersistentSessions()); + await testEnvironment.StartServer(o => o.WithPersistentSessions()); - const string clientId = "Client1"; + const string clientId = "Client1"; - // Create client with clean session and long session expiry interval + // Create client with clean session and long session expiry interval - var client1 = await testEnvironment.ConnectClient( - o => o.WithProtocolVersion(MqttProtocolVersion.V311) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact - .WithCleanSession() // start and end with clean session - .WithClientId(clientId) - .Build()); + var client1 = await testEnvironment.ConnectClient( + o => o.WithProtocolVersion(MqttProtocolVersion.V311) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact + .WithCleanSession() // start and end with clean session + .WithClientId(clientId) + .Build()); - // Disconnect; empty session should be removed from server + // Disconnect; empty session should be removed from server - await client1.DisconnectAsync(); + await client1.DisconnectAsync(); - // Simulate some time delay between connections + // Simulate some time delay between connections - await Task.Delay(1000); + await Task.Delay(1000); - // Reconnect the same client ID without clean session + // Reconnect the same client ID without clean session - var client2 = testEnvironment.CreateClient(); - var options = testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V311) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact - .WithCleanSession(false) // see if there is a session - .WithClientId(clientId) - .Build(); + var client2 = testEnvironment.CreateClient(); + var options = testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V311) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact + .WithCleanSession(false) // see if there is a session + .WithClientId(clientId) + .Build(); - var result = await client2.ConnectAsync(options).ConfigureAwait(false); + var result = await client2.ConnectAsync(options).ConfigureAwait(false); - await client2.DisconnectAsync(); + await client2.DisconnectAsync(); - // Session should NOT be present for MQTT v311 and initial CleanSession == true + // Session should NOT be present for MQTT v311 and initial CleanSession == true - Assert.IsTrue(!result.IsSessionPresent, "Session present"); - } - } + Assert.IsTrue(!result.IsSessionPresent, "Session present"); + } - [TestMethod] - public async Task Do_Not_Use_Expired_Session() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(o => o.WithPersistentSessions()); + [TestMethod] + public async Task Do_Not_Use_Expired_Session() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(o => o.WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3)); + var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3)); - // Kill the client connection and ensure that the session will stay there but is expired after 3 seconds! - c1.Dispose(); + // Kill the client connection and ensure that the session will stay there but is expired after 3 seconds! + c1.Dispose(); - await Task.Delay(TimeSpan.FromSeconds(6)); + await Task.Delay(TimeSpan.FromSeconds(6)); - c1 = testEnvironment.CreateClient(); - var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build(); + c1 = testEnvironment.CreateClient(); + var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build(); - var connectResult = await c1.ConnectAsync(options); - Assert.AreEqual(false, connectResult.IsSessionPresent); - } - } + var connectResult = await c1.ConnectAsync(options); + Assert.AreEqual(false, connectResult.IsSessionPresent); + } + + [TestMethod] + public async Task Fire_Deleted_Event() + { + using var testEnvironment = CreateTestEnvironment(); + // Arrange client and server. + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions(false)); - [TestMethod] - public async Task Fire_Deleted_Event() + var deletedEventFired = false; + server.SessionDeletedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - // Arrange client and server. - var server = await testEnvironment.StartServer(o => o.WithPersistentSessions(false)); + deletedEventFired = true; + return CompletedTask.Instance; + }; - var deletedEventFired = false; - server.SessionDeletedAsync += e => - { - deletedEventFired = true; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); - var client = await testEnvironment.ConnectClient(); + // Act: Disconnect the client -> Event must be fired. + await client.DisconnectAsync(); - // Act: Disconnect the client -> Event must be fired. - await client.DisconnectAsync(); + await LongTestDelay(); - await LongTestDelay(); + // Assert that the event was fired properly. + Assert.IsTrue(deletedEventFired); + } - // Assert that the event was fired properly. - Assert.IsTrue(deletedEventFired); - } - } + [TestMethod] + public async Task Get_Session_Items_In_Status() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Get_Session_Items_In_Status() + server.ValidatingConnectionAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + // Don't validate anything. Just set some session items. + e.SessionItems["can_subscribe_x"] = true; + e.SessionItems["default_payload"] = "Hello World"; - server.ValidatingConnectionAsync += e => - { - // Don't validate anything. Just set some session items. - e.SessionItems["can_subscribe_x"] = true; - e.SessionItems["default_payload"] = "Hello World"; + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + await testEnvironment.ConnectClient(); - await testEnvironment.ConnectClient(); + var sessionStatus = await testEnvironment.Server.GetSessionsAsync(); + var session = sessionStatus.First(); - var sessionStatus = await testEnvironment.Server.GetSessionsAsync(); - var session = sessionStatus.First(); + Assert.AreEqual(true, session.Items["can_subscribe_x"]); + } - Assert.AreEqual(true, session.Items["can_subscribe_x"]); - } - } + [TestMethod] + [DataRow(MqttProtocolVersion.V310)] + [DataRow(MqttProtocolVersion.V311)] + [DataRow(MqttProtocolVersion.V500)] + public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion) + { + using var testEnvironment = CreateTestEnvironment(); + testEnvironment.IgnoreClientLogErrors = true; - [TestMethod] - [DataRow(MqttProtocolVersion.V310)] - [DataRow(MqttProtocolVersion.V311)] - [DataRow(MqttProtocolVersion.V500)] - public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion) - { - using (var testEnvironment = CreateTestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; + await testEnvironment.StartServer(); - await testEnvironment.StartServer(); + var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion); - var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion); + var hasReceive = false; - var hasReceive = false; - void OnReceive() - { - hasReceive = true; - } + // Try to connect 50 clients at the same time. + var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(_ => ConnectAndSubscribe(testEnvironment, options, OnReceive))); - // Try to connect 50 clients at the same time. - var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive))); + var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList(); - var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList(); + await LongTestDelay(); - await LongTestDelay(); + Assert.AreEqual(1, connectedClients.Count); - Assert.AreEqual(1, connectedClients.Count); + var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10)); + var sendClient = await testEnvironment.ConnectClient(option2); + await sendClient.PublishStringAsync("aaa", "1"); - var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10)); - var sendClient = await testEnvironment.ConnectClient(option2); - await sendClient.PublishStringAsync("aaa", "1"); + await LongTestDelay(); - await LongTestDelay(); + Assert.AreEqual(true, hasReceive); + return; - Assert.AreEqual(true, hasReceive); - } + void OnReceive() + { + hasReceive = true; } + } - [TestMethod] - [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] - [DataRow(MqttQualityOfServiceLevel.AtLeastOnce)] - public async Task Retry_If_Not_PubAck(MqttQualityOfServiceLevel qos) + [TestMethod] + [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] + [DataRow(MqttQualityOfServiceLevel.AtLeastOnce)] + public async Task Retry_If_Not_PubAck(MqttQualityOfServiceLevel qos) + { + long count = 0; + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(o => o.WithPersistentSessions()); + + var publisher = await testEnvironment.ConnectClient(); + + var subscriber = await testEnvironment.ConnectClient(o => o.WithClientId(qos.ToString()).WithCleanSession(false)); + + subscriber.ApplicationMessageReceivedAsync += c => { - long count = 0; - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(o => o.WithPersistentSessions()); + c.AutoAcknowledge = false; + ++count; + Console.WriteLine("process"); + return CompletedTask.Instance; + }; - var publisher = await testEnvironment.ConnectClient(); + await subscriber.SubscribeAsync("#", qos); - var subscriber = await testEnvironment.ConnectClient(o => o.WithClientId(qos.ToString()).WithCleanSession(false)); + var pub = publisher.PublishStringAsync("a", null, qos); - subscriber.ApplicationMessageReceivedAsync += c => - { - c.AutoAcknowledge = false; - ++count; - Console.WriteLine("process"); - return CompletedTask.Instance; - }; + await Task.Delay(100); + await subscriber.DisconnectAsync(); + await subscriber.ConnectAsync(subscriber.Options); + await Task.Delay(100); - await subscriber.SubscribeAsync("#", qos); + var res = await pub; - var pub = publisher.PublishStringAsync("a", null, qos); + Assert.AreEqual(MqttClientPublishReasonCode.Success, res.ReasonCode); + Assert.AreEqual(2, count); + } - await Task.Delay(100); - await subscriber.DisconnectAsync(); - await subscriber.ConnectAsync(subscriber.Options); - await Task.Delay(100); + [TestMethod] + public async Task Session_Takeover() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var res = await pub; + var options = new MqttClientOptionsBuilder().WithCleanSession(false) + .WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+ + .WithClientId("a"); - Assert.AreEqual(MqttClientPublishReasonCode.Success, res.ReasonCode); - Assert.AreEqual(2, count); - } - } + var client1 = await testEnvironment.ConnectClient(options); + await Task.Delay(500); - [TestMethod] - public async Task Session_Takeover() + var disconnectReason = MqttClientDisconnectReason.NormalDisconnection; + client1.DisconnectedAsync += c => { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + disconnectReason = c.Reason; + return CompletedTask.Instance; + }; - var options = new MqttClientOptionsBuilder().WithCleanSession(false) - .WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+ - .WithClientId("a"); + var client2 = await testEnvironment.ConnectClient(options); + await Task.Delay(500); - var client1 = await testEnvironment.ConnectClient(options); - await Task.Delay(500); + Assert.IsFalse(client1.IsConnected); + Assert.IsTrue(client2.IsConnected); - var disconnectReason = MqttClientDisconnectReason.NormalDisconnection; - client1.DisconnectedAsync += c => - { - disconnectReason = c.Reason; - return CompletedTask.Instance; - }; + Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason); + } - var client2 = await testEnvironment.ConnectClient(options); - await Task.Delay(500); + [TestMethod] + public async Task Set_Session_Item() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - Assert.IsFalse(client1.IsConnected); - Assert.IsTrue(client2.IsConnected); + server.ValidatingConnectionAsync += e => + { + // Don't validate anything. Just set some session items. + e.SessionItems["can_subscribe_x"] = true; + e.SessionItems["default_payload"] = "Hello World"; - Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason); - } - } + return CompletedTask.Instance; + }; - [TestMethod] - public async Task Set_Session_Item() + server.InterceptingSubscriptionAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) + if (e.TopicFilter.Topic == "x") { - var server = await testEnvironment.StartServer(); - - server.ValidatingConnectionAsync += e => + if (e.SessionItems["can_subscribe_x"] as bool? == false) { - // Don't validate anything. Just set some session items. - e.SessionItems["can_subscribe_x"] = true; - e.SessionItems["default_payload"] = "Hello World"; + e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError; + } + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - server.InterceptingSubscriptionAsync += e => - { - if (e.TopicFilter.Topic == "x") - { - if (e.SessionItems["can_subscribe_x"] as bool? == false) - { - e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError; - } - } - - return CompletedTask.Instance; - }; - - server.InterceptingPublishAsync += e => - { - e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty)); - return CompletedTask.Instance; - }; + server.InterceptingPublishAsync += e => + { + e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty)); + return CompletedTask.Instance; + }; - string receivedPayload = null; + string receivedPayload = null; - var client = await testEnvironment.ConnectClient(); - client.ApplicationMessageReceivedAsync += e => - { - receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); + client.ApplicationMessageReceivedAsync += e => + { + receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); + return CompletedTask.Instance; + }; - var subscribeResult = await client.SubscribeAsync("x"); + var subscribeResult = await client.SubscribeAsync("x"); - Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items.First().ResultCode); + Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items.First().ResultCode); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("x"); + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("x"); - await Task.Delay(1000); + await Task.Delay(1000); - Assert.AreEqual("Hello World", receivedPayload); - } - } + Assert.AreEqual("Hello World", receivedPayload); + } - [TestMethod] - public async Task Use_Clean_Session() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Use_Clean_Session() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build()); + var client = testEnvironment.CreateClient(); + var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build()); - // Create the session including the subscription. - var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false)); - await client1.SubscribeAsync("x"); - await client1.DisconnectAsync(); - await Task.Delay(500); + // Create the session including the subscription. + var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false)); + await client1.SubscribeAsync("x"); + await client1.DisconnectAsync(); + await Task.Delay(500); - Assert.IsFalse(connectResult.IsSessionPresent); - } - } + Assert.IsFalse(connectResult.IsSessionPresent); + } - [TestMethod] - public async Task Will_Message_Do_Not_Send_On_Takeover() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var receivedMessagesCount = 0; + [TestMethod] + public async Task Will_Message_Do_Not_Send_On_Takeover() + { + using var testEnvironment = CreateTestEnvironment(); + var receivedMessagesCount = 0; - await testEnvironment.StartServer(); + await testEnvironment.StartServer(); - // C1 will receive the last will! - var c1 = await testEnvironment.ConnectClient(); - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + // C1 will receive the last will! + var c1 = await testEnvironment.ConnectClient(); + c1.ApplicationMessageReceivedAsync += _ => + { + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); + await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); - // C2 has the last will defined. - var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner"); + // C2 has the last will defined. + var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner"); - await testEnvironment.ConnectClient(clientOptions); + await testEnvironment.ConnectClient(clientOptions); - // C3 will do the connection takeover. - await testEnvironment.ConnectClient(clientOptions); + // C3 will do the connection takeover. + await testEnvironment.ConnectClient(clientOptions); - await Task.Delay(1000); + await Task.Delay(1000); - Assert.AreEqual(0, receivedMessagesCount); - } - } + Assert.AreEqual(0, receivedMessagesCount); + } - static async Task ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive) + static async Task ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive) + { + try { - try - { - var client = await testEnvironment.ConnectClient(options).ConfigureAwait(false); + var client = await testEnvironment.ConnectClient(options).ConfigureAwait(false); - client.ApplicationMessageReceivedAsync += e => - { - onReceive(); - return CompletedTask.Instance; - }; + client.ApplicationMessageReceivedAsync += _ => + { + onReceive(); + return CompletedTask.Instance; + }; - using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30))) - { - await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false); - return client; - } - catch (Exception) - { - return null; - } + return client; + } + catch + { + return null; } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Shared_Subscriptions_Tests.cs b/Source/MQTTnet.Tests/Server/Shared_Subscriptions_Tests.cs index c1a39289d..f2f5fe557 100644 --- a/Source/MQTTnet.Tests/Server/Shared_Subscriptions_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Shared_Subscriptions_Tests.cs @@ -7,43 +7,39 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Shared_Subscriptions_Tests : BaseTestClass { - [TestClass] - public sealed class Shared_Subscriptions_Tests : BaseTestClass + [TestMethod] + public async Task Server_Reports_Shared_Subscriptions_Not_Supported() { - [TestMethod] - public async Task Server_Reports_Shared_Subscriptions_Not_Supported() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); - - var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - - Assert.IsFalse(connectResult.SharedSubscriptionAvailable); - } - } - - [TestMethod] - public async Task Subscription_Of_Shared_Subscription_Is_Denied() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); - - var client = testEnvironment.CreateClient(); - await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - - var subscribeResult = await client.SubscribeAsync("$share/A"); - - Assert.AreEqual(MqttClientSubscribeResultCode.SharedSubscriptionsNotSupported, subscribeResult.Items.First().ResultCode); - } - } + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); + + var client = testEnvironment.CreateClient(); + var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + + Assert.IsFalse(connectResult.SharedSubscriptionAvailable); + } + + [TestMethod] + public async Task Subscription_Of_Shared_Subscription_Is_Denied() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); + + var client = testEnvironment.CreateClient(); + await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + + var subscribeResult = await client.SubscribeAsync("$share/A"); + + Assert.AreEqual(MqttClientSubscribeResultCode.SharedSubscriptionsNotSupported, subscribeResult.Items.First().ResultCode); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Status_Tests.cs b/Source/MQTTnet.Tests/Server/Status_Tests.cs index ef9419eb3..816705476 100644 --- a/Source/MQTTnet.Tests/Server/Status_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Status_Tests.cs @@ -12,189 +12,178 @@ namespace MQTTnet.Tests.Server; +// ReSharper disable InconsistentNaming [TestClass] public sealed class Status_Tests : BaseTestClass { [TestMethod] public async Task Disconnect_Client() { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); - await Task.Delay(1000); + await Task.Delay(1000); - var clientStatus = await server.GetClientsAsync(); + var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(1, clientStatus.Count); - Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); + Assert.AreEqual(1, clientStatus.Count); + Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); - await clientStatus.First().DisconnectAsync(); + await clientStatus[0].DisconnectAsync(); - await Task.Delay(500); + await Task.Delay(500); - Assert.IsFalse(c1.IsConnected); + Assert.IsFalse(c1.IsConnected); - clientStatus = await server.GetClientsAsync(); + clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(0, clientStatus.Count); - } + Assert.AreEqual(0, clientStatus.Count); } [TestMethod] public async Task Keep_Persistent_Session_Version311() { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient( - new MqttClientOptionsBuilder().WithClientId("client1").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V311)); - var c2 = await testEnvironment.ConnectClient( - new MqttClientOptionsBuilder().WithClientId("client2").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V311)); + var c1 = await testEnvironment.ConnectClient( + new MqttClientOptionsBuilder().WithClientId("client1").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V311)); + var c2 = await testEnvironment.ConnectClient( + new MqttClientOptionsBuilder().WithClientId("client2").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V311)); - await c1.DisconnectAsync(); + await c1.DisconnectAsync(); - await LongTestDelay(); + await LongTestDelay(); - var clientStatus = await server.GetClientsAsync(); - var sessionStatus = await server.GetSessionsAsync(); + var clientStatus = await server.GetClientsAsync(); + var sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(1, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); + Assert.AreEqual(1, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); - await c2.DisconnectAsync(); + await c2.DisconnectAsync(); - await LongTestDelay(); + await LongTestDelay(); - clientStatus = await server.GetClientsAsync(); - sessionStatus = await server.GetSessionsAsync(); + clientStatus = await server.GetClientsAsync(); + sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(0, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); - } + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); } [TestMethod] public async Task Keep_Persistent_Session_Version500() { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient( - new MqttClientOptionsBuilder().WithClientId("client1").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V500)); - var c2 = await testEnvironment.ConnectClient( - new MqttClientOptionsBuilder().WithClientId("client2").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V500)); + var c1 = await testEnvironment.ConnectClient( + new MqttClientOptionsBuilder().WithClientId("client1").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V500)); + var c2 = await testEnvironment.ConnectClient( + new MqttClientOptionsBuilder().WithClientId("client2").WithCleanSession(false).WithProtocolVersion(MqttProtocolVersion.V500)); - // The session expiry interval is mandatory for MQTT5.0.0 in order keep session! - await c1.DisconnectAsync(sessionExpiryInterval: 60); + // The session expiry interval is mandatory for MQTT5.0.0 in order keep session! + await c1.DisconnectAsync(sessionExpiryInterval: 60); - await LongTestDelay(); + await LongTestDelay(); - var clientStatus = await server.GetClientsAsync(); - var sessionStatus = await server.GetSessionsAsync(); + var clientStatus = await server.GetClientsAsync(); + var sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(1, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); + Assert.AreEqual(1, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); - // The session expiry interval is mandatory for MQTT5.0.0 in order keep session! - await c2.DisconnectAsync(sessionExpiryInterval: 60); + // The session expiry interval is mandatory for MQTT5.0.0 in order keep session! + await c2.DisconnectAsync(sessionExpiryInterval: 60); - await LongTestDelay(); + await LongTestDelay(); - clientStatus = await server.GetClientsAsync(); - sessionStatus = await server.GetSessionsAsync(); + clientStatus = await server.GetClientsAsync(); + sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(0, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); - } + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); } [TestMethod] public async Task Show_Client_And_Session_Statistics() { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); - var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2")); - await Task.Delay(500); + await Task.Delay(500); - var clientStatus = await server.GetClientsAsync(); - var sessionStatus = await server.GetSessionsAsync(); + var clientStatus = await server.GetClientsAsync(); + var sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(2, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); + Assert.AreEqual(2, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); - Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); - Assert.IsTrue(clientStatus.Any(s => s.Id == c2.Options.ClientId)); + Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); + Assert.IsTrue(clientStatus.Any(s => s.Id == c2.Options.ClientId)); - await c1.DisconnectAsync(); - await c2.DisconnectAsync(); + await c1.DisconnectAsync(); + await c2.DisconnectAsync(); - await Task.Delay(500); + await Task.Delay(500); - clientStatus = await server.GetClientsAsync(); - sessionStatus = await server.GetSessionsAsync(); + clientStatus = await server.GetClientsAsync(); + sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(0, clientStatus.Count); - Assert.AreEqual(0, sessionStatus.Count); - } + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(0, sessionStatus.Count); } [TestMethod] public async Task Track_Sent_Application_Messages() { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient(); + var c1 = await testEnvironment.ConnectClient(); - for (var i = 1; i < 25; i++) - { - await c1.PublishStringAsync("a"); - await Task.Delay(50); + for (var i = 1; i < 25; i++) + { + await c1.PublishStringAsync("a"); + await Task.Delay(50); - var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount); - Assert.AreEqual(0, clientStatus.First().ReceivedApplicationMessagesCount); - } + var clientStatus = await server.GetClientsAsync(); + Assert.AreEqual(i, clientStatus[0].SentApplicationMessagesCount); + Assert.AreEqual(0, clientStatus[0].ReceivedApplicationMessagesCount); } } [TestMethod] public async Task Track_Sent_Packets() { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + using var testEnvironment = new TestEnvironment(TestContext); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithNoKeepAlive()); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithNoKeepAlive()); - for (var i = 1; i < 25; i++) - { - // At most once will send one packet to the client and the server will reply - // with an additional ACK packet. - await c1.PublishStringAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce); + for (var i = 1; i < 25; i++) + { + // At most once will send one packet to the client and the server will reply + // with an additional ACK packet. + await c1.PublishStringAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce); - await Task.Delay(500); + await Task.Delay(500); - var clientStatus = await server.GetClientsAsync(); + var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount, "SAMC invalid!"); + Assert.AreEqual(i, clientStatus[0].SentApplicationMessagesCount, "SAMC invalid!"); - // + 1 because CONNECT is also counted. - Assert.AreEqual(i + 1, clientStatus.First().SentPacketsCount, "SPC invalid!"); + // + 1 because CONNECT is also counted. + Assert.AreEqual(i + 1, clientStatus[0].SentPacketsCount, "SPC invalid!"); - // +2 because ConnACK + PubAck package is already counted. - Assert.AreEqual(i + 2, clientStatus.First().ReceivedPacketsCount, "RPC invalid!"); - } + // +2 because ConnACK + PubAck package is already counted. + Assert.AreEqual(i + 2, clientStatus[0].ReceivedPacketsCount, "RPC invalid!"); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs b/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs index 9dd48c317..dfd95765f 100644 --- a/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs @@ -15,420 +15,396 @@ using MQTTnet.Server; using MQTTnet.Tests.Helpers; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Subscribe_Tests : BaseTestClass { - [TestClass] - public sealed class Subscribe_Tests : BaseTestClass + [TestMethod] + [DataRow("A", "A", true)] + [DataRow("A", "B", false)] + [DataRow("A", "#", true)] + [DataRow("A", "+", true)] + [DataRow("A/B", "A/B", true)] + [DataRow("A/B", "A/+", true)] + [DataRow("A/B", "A/#", true)] + [DataRow("A/B/C", "A/B/C", true)] + [DataRow("A/B/C", "A/+/C", true)] + [DataRow("A/B/C", "A/+/+", true)] + [DataRow("A/B/C", "A/+/#", true)] + [DataRow("A/B/C/D", "A/B/C/D", true)] + [DataRow("A/B/C/D", "A/+/C/+", true)] + [DataRow("A/B/C/D", "A/+/C/#", true)] + [DataRow("A/B/C", "A/B/+", true)] + [DataRow("A/B1/B2/C", "A/+/C", false)] + public async Task Subscription_Roundtrip(string topic, string filter, bool shouldWork) { - [TestMethod] - [DataRow("A", "A", true)] - [DataRow("A", "B", false)] - [DataRow("A", "#", true)] - [DataRow("A", "+", true)] - [DataRow("A/B", "A/B", true)] - [DataRow("A/B", "A/+", true)] - [DataRow("A/B", "A/#", true)] - [DataRow("A/B/C", "A/B/C", true)] - [DataRow("A/B/C", "A/+/C", true)] - [DataRow("A/B/C", "A/+/+", true)] - [DataRow("A/B/C", "A/+/#", true)] - [DataRow("A/B/C/D", "A/B/C/D", true)] - [DataRow("A/B/C/D", "A/+/C/+", true)] - [DataRow("A/B/C/D", "A/+/C/#", true)] - [DataRow("A/B/C", "A/B/+", true)] - [DataRow("A/B1/B2/C", "A/+/C", false)] - public async Task Subscription_Roundtrip(string topic, string filter, bool shouldWork) - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer().ConfigureAwait(false); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer().ConfigureAwait(false); - var receiver = await testEnvironment.ConnectClient(); - await receiver.SubscribeAsync(filter).ConfigureAwait(false); - var receivedMessages = receiver.TrackReceivedMessages(); + var receiver = await testEnvironment.ConnectClient(); + await receiver.SubscribeAsync(filter).ConfigureAwait(false); + var receivedMessages = receiver.TrackReceivedMessages(); - var sender = await testEnvironment.ConnectClient(); - await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false); + var sender = await testEnvironment.ConnectClient(); + await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false); - await LongTestDelay().ConfigureAwait(false); + await LongTestDelay().ConfigureAwait(false); - if (shouldWork) - { - Assert.AreEqual(1, receivedMessages.Count, message: "The filter should work!"); - } - else - { - Assert.AreEqual(0, receivedMessages.Count, message: "The filter should not work!"); - } - } + if (shouldWork) + { + Assert.AreEqual(1, receivedMessages.Count, message: "The filter should work!"); } + else + { + Assert.AreEqual(0, receivedMessages.Count, message: "The filter should not work!"); + } + } - [TestMethod] - public async Task Deny_Invalid_Topic() + [TestMethod] + public async Task Deny_Invalid_Topic() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); + + server.InterceptingSubscriptionAsync += e => { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) + if (e.TopicFilter.Topic == "not_allowed_topic") { - var server = await testEnvironment.StartServer(); + e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid; + } - server.InterceptingSubscriptionAsync += e => - { - if (e.TopicFilter.Topic == "not_allowed_topic") - { - e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid; - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); - var client = await testEnvironment.ConnectClient(); + var subscribeResult = await client.SubscribeAsync("allowed_topic"); + Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items.First().ResultCode); - var subscribeResult = await client.SubscribeAsync("allowed_topic"); - Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items.First().ResultCode); + subscribeResult = await client.SubscribeAsync("not_allowed_topic"); + Assert.AreEqual(MqttClientSubscribeResultCode.TopicFilterInvalid, subscribeResult.Items.First().ResultCode); + } - subscribeResult = await client.SubscribeAsync("not_allowed_topic"); - Assert.AreEqual(MqttClientSubscribeResultCode.TopicFilterInvalid, subscribeResult.Items.First().ResultCode); - } - } + [TestMethod] + public async Task Intercept_Subscribe_With_User_Properties() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Intercept_Subscribe_With_User_Properties() + InterceptingSubscriptionEventArgs eventArgs = null; + server.InterceptingSubscriptionAsync += e => { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - var server = await testEnvironment.StartServer(); + eventArgs = e; + return CompletedTask.Instance; + }; - InterceptingSubscriptionEventArgs eventArgs = null; - server.InterceptingSubscriptionAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); - var client = await testEnvironment.ConnectClient(); + var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build(); + await client.SubscribeAsync(subscribeOptions); - var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build(); - await client.SubscribeAsync(subscribeOptions); + CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties); + } - CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties); - } - } + [TestMethod] + [ExpectedException(typeof(MqttClientDisconnectedException))] + public async Task Disconnect_While_Subscribing() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - [ExpectedException(typeof(MqttClientDisconnectedException))] - public async Task Disconnect_While_Subscribing() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + // The client will be disconnected directly after subscribing! + server.ClientSubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId); - // The client will be disconnect directly after subscribing! - server.ClientSubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection); + var client = await testEnvironment.ConnectClient(); + await client.SubscribeAsync("#"); + } - var client = await testEnvironment.ConnectClient(); - await client.SubscribeAsync("#"); - } - } + [TestMethod] + public async Task Enqueue_Message_After_Subscription() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Enqueue_Message_After_Subscription() + server.ClientSubscribedTopicAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build())); + return CompletedTask.Instance; + }; - server.ClientSubscribedTopicAsync += e => - { - server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build())); - return CompletedTask.Instance; - }; + var client = await testEnvironment.ConnectClient(); + var receivedMessages = testEnvironment.CreateApplicationMessageHandler(client); - var client = await testEnvironment.ConnectClient(); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(client); + await client.SubscribeAsync("test_topic"); - await client.SubscribeAsync("test_topic"); + await LongTestDelay(); - await LongTestDelay(); + Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count); + } - Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count); - } - } + [TestMethod] + public async Task Intercept_Subscription() + { + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Intercept_Subscription() + server.InterceptingSubscriptionAsync += e => { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + // Set the topic to "a" regards what the client wants to subscribe. + e.TopicFilter.Topic = "a"; + return CompletedTask.Instance; + }; - server.InterceptingSubscriptionAsync += e => - { - // Set the topic to "a" regards what the client wants to subscribe. - e.TopicFilter.Topic = "a"; - return CompletedTask.Instance; - }; + var topicAReceived = false; + var topicBReceived = false; - var topicAReceived = false; - var topicBReceived = false; + var client = await testEnvironment.ConnectClient(); + client.ApplicationMessageReceivedAsync += e => + { + if (e.ApplicationMessage.Topic == "a") + { + topicAReceived = true; + } + else if (e.ApplicationMessage.Topic == "b") + { + topicBReceived = true; + } - var client = await testEnvironment.ConnectClient(); - client.ApplicationMessageReceivedAsync += e => - { - if (e.ApplicationMessage.Topic == "a") - { - topicAReceived = true; - } - else if (e.ApplicationMessage.Topic == "b") - { - topicBReceived = true; - } + return CompletedTask.Instance; + }; - return CompletedTask.Instance; - }; + await client.SubscribeAsync("b"); - await client.SubscribeAsync("b"); + await client.PublishStringAsync("a"); - await client.PublishStringAsync("a"); + await Task.Delay(500); - await Task.Delay(500); + Assert.IsTrue(topicAReceived); + Assert.IsFalse(topicBReceived); + } - Assert.IsTrue(topicAReceived); - Assert.IsFalse(topicBReceived); - } - } + [TestMethod] + public async Task Response_Contains_Equal_Reason_Codes() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); - [TestMethod] - public async Task Response_Contains_Equal_Reason_Codes() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClient(); + var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a") + .WithTopicFilter("b", MqttQualityOfServiceLevel.AtLeastOnce) + .WithTopicFilter("c", MqttQualityOfServiceLevel.ExactlyOnce) + .WithTopicFilter("d") + .Build(); - var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a") - .WithTopicFilter("b", MqttQualityOfServiceLevel.AtLeastOnce) - .WithTopicFilter("c", MqttQualityOfServiceLevel.ExactlyOnce) - .WithTopicFilter("d") - .Build(); + var response = await client.SubscribeAsync(subscribeOptions); - var response = await client.SubscribeAsync(subscribeOptions); + Assert.AreEqual(subscribeOptions.TopicFilters.Count, response.Items.Count); + } - Assert.AreEqual(subscribeOptions.TopicFilters.Count, response.Items.Count); - } - } + [TestMethod] + public async Task Subscribe_Lots_In_Multiple_Requests() + { + using var testEnvironment = CreateTestEnvironment(); + var receivedMessagesCount = 0; + + await testEnvironment.StartServer(); - [TestMethod] - public async Task Subscribe_Lots_In_Multiple_Requests() + var c1 = await testEnvironment.ConnectClient(); + c1.ApplicationMessageReceivedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var receivedMessagesCount = 0; + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - await testEnvironment.StartServer(); + for (var i = 0; i < 500; i++) + { + var so = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(i.ToString()).Build(); - var c1 = await testEnvironment.ConnectClient(); - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + await c1.SubscribeAsync(so).ConfigureAwait(false); - for (var i = 0; i < 500; i++) - { - var so = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(i.ToString()).Build(); + await Task.Delay(10); + } - await c1.SubscribeAsync(so).ConfigureAwait(false); + var c2 = await testEnvironment.ConnectClient(); - await Task.Delay(10); - } + var messageBuilder = new MqttApplicationMessageBuilder(); + for (var i = 0; i < 500; i++) + { + messageBuilder.WithTopic(i.ToString()); - var c2 = await testEnvironment.ConnectClient(); + await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false); - var messageBuilder = new MqttApplicationMessageBuilder(); - for (var i = 0; i < 500; i++) - { - messageBuilder.WithTopic(i.ToString()); + await Task.Delay(10); + } - await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false); + SpinWait.SpinUntil(() => receivedMessagesCount == 500, 5000); - await Task.Delay(10); - } + Assert.AreEqual(500, receivedMessagesCount); + } - SpinWait.SpinUntil(() => receivedMessagesCount == 500, 5000); + [TestMethod] + public async Task Subscribe_Lots_In_Single_Request() + { + using var testEnvironment = CreateTestEnvironment(); + var receivedMessagesCount = 0; - Assert.AreEqual(500, receivedMessagesCount); - } - } + await testEnvironment.StartServer(); - [TestMethod] - public async Task Subscribe_Lots_In_Single_Request() + var c1 = await testEnvironment.ConnectClient(); + c1.ApplicationMessageReceivedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var receivedMessagesCount = 0; + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - await testEnvironment.StartServer(); + var optionsBuilder = new MqttClientSubscribeOptionsBuilder(); + for (var i = 0; i < 500; i++) + { + optionsBuilder.WithTopicFilter(i.ToString()); + } - var c1 = await testEnvironment.ConnectClient(); - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + await c1.SubscribeAsync(optionsBuilder.Build()).ConfigureAwait(false); - var optionsBuilder = new MqttClientSubscribeOptionsBuilder(); - for (var i = 0; i < 500; i++) - { - optionsBuilder.WithTopicFilter(i.ToString()); - } + var c2 = await testEnvironment.ConnectClient(); - await c1.SubscribeAsync(optionsBuilder.Build()).ConfigureAwait(false); + var messageBuilder = new MqttApplicationMessageBuilder(); + for (var i = 0; i < 500; i++) + { + messageBuilder.WithTopic(i.ToString()); - var c2 = await testEnvironment.ConnectClient(); + await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false); + } - var messageBuilder = new MqttApplicationMessageBuilder(); - for (var i = 0; i < 500; i++) - { - messageBuilder.WithTopic(i.ToString()); + SpinWait.SpinUntil(() => receivedMessagesCount == 500, TimeSpan.FromSeconds(20)); - await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false); - } + Assert.AreEqual(500, receivedMessagesCount); + } - SpinWait.SpinUntil(() => receivedMessagesCount == 500, TimeSpan.FromSeconds(20)); + [TestMethod] + public async Task Subscribe_Multiple_In_Multiple_Request() + { + using var testEnvironment = CreateTestEnvironment(); + var receivedMessagesCount = 0; - Assert.AreEqual(500, receivedMessagesCount); - } - } + await testEnvironment.StartServer(); - [TestMethod] - public async Task Subscribe_Multiple_In_Multiple_Request() + var c1 = await testEnvironment.ConnectClient(); + c1.ApplicationMessageReceivedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var receivedMessagesCount = 0; + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - await testEnvironment.StartServer(); + await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build()); - var c1 = await testEnvironment.ConnectClient(); - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("b").Build()); - await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build()); + await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("c").Build()); - await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("b").Build()); + var c2 = await testEnvironment.ConnectClient(); - await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("c").Build()); + await c2.PublishStringAsync("a"); + await Task.Delay(100); + Assert.AreEqual(receivedMessagesCount, 1); - var c2 = await testEnvironment.ConnectClient(); + await c2.PublishStringAsync("b"); + await Task.Delay(100); + Assert.AreEqual(receivedMessagesCount, 2); - await c2.PublishStringAsync("a"); - await Task.Delay(100); - Assert.AreEqual(receivedMessagesCount, 1); + await c2.PublishStringAsync("c"); + await Task.Delay(100); + Assert.AreEqual(receivedMessagesCount, 3); + } - await c2.PublishStringAsync("b"); - await Task.Delay(100); - Assert.AreEqual(receivedMessagesCount, 2); + [TestMethod] + public async Task Subscribe_Multiple_In_Single_Request() + { + using var testEnvironment = CreateTestEnvironment(); + var receivedMessagesCount = 0; - await c2.PublishStringAsync("c"); - await Task.Delay(100); - Assert.AreEqual(receivedMessagesCount, 3); - } - } + await testEnvironment.StartServer(); - [TestMethod] - public async Task Subscribe_Multiple_In_Single_Request() + var c1 = await testEnvironment.ConnectClient(); + c1.ApplicationMessageReceivedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var receivedMessagesCount = 0; + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - await testEnvironment.StartServer(); + await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build()); - var c1 = await testEnvironment.ConnectClient(); - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + var c2 = await testEnvironment.ConnectClient(); - await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build()); + await c2.PublishStringAsync("a"); + await Task.Delay(100); + Assert.AreEqual(receivedMessagesCount, 1); - var c2 = await testEnvironment.ConnectClient(); + await c2.PublishStringAsync("b"); + await Task.Delay(100); + Assert.AreEqual(receivedMessagesCount, 2); - await c2.PublishStringAsync("a"); - await Task.Delay(100); - Assert.AreEqual(receivedMessagesCount, 1); + await c2.PublishStringAsync("c"); + await Task.Delay(100); + Assert.AreEqual(receivedMessagesCount, 3); + } - await c2.PublishStringAsync("b"); - await Task.Delay(100); - Assert.AreEqual(receivedMessagesCount, 2); + [TestMethod] + public async Task Subscribe_Unsubscribe() + { + using var testEnvironment = CreateTestEnvironment(); + var receivedMessagesCount = 0; - await c2.PublishStringAsync("c"); - await Task.Delay(100); - Assert.AreEqual(receivedMessagesCount, 3); - } - } + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Subscribe_Unsubscribe() + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c1")); + c1.ApplicationMessageReceivedAsync += _ => { - using (var testEnvironment = CreateTestEnvironment()) - { - var receivedMessagesCount = 0; - - var server = await testEnvironment.StartServer(); + Interlocked.Increment(ref receivedMessagesCount); + return CompletedTask.Instance; + }; - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c1")); - c1.ApplicationMessageReceivedAsync += e => - { - Interlocked.Increment(ref receivedMessagesCount); - return CompletedTask.Instance; - }; + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2")); - var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2")); + var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build(); + await c2.PublishAsync(message); - var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build(); - await c2.PublishAsync(message); + await Task.Delay(500); + Assert.AreEqual(0, receivedMessagesCount); - await Task.Delay(500); - Assert.AreEqual(0, receivedMessagesCount); - - var subscribeEventCalled = false; - server.ClientSubscribedTopicAsync += e => - { - subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId; - return CompletedTask.Instance; - }; + var subscribeEventCalled = false; + server.ClientSubscribedTopicAsync += e => + { + subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId; + return CompletedTask.Instance; + }; - await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - await Task.Delay(250); - Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); + await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + await Task.Delay(250); + Assert.IsTrue(subscribeEventCalled, "Subscribe event not called."); - await c2.PublishAsync(message); - await Task.Delay(250); - Assert.AreEqual(1, receivedMessagesCount); + await c2.PublishAsync(message); + await Task.Delay(250); + Assert.AreEqual(1, receivedMessagesCount); - var unsubscribeEventCalled = false; - server.ClientUnsubscribedTopicAsync += e => - { - unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId; - return CompletedTask.Instance; - }; + var unsubscribeEventCalled = false; + server.ClientUnsubscribedTopicAsync += e => + { + unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId; + return CompletedTask.Instance; + }; - await c1.UnsubscribeAsync("a"); - await Task.Delay(250); - Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called."); + await c1.UnsubscribeAsync("a"); + await Task.Delay(250); + Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called."); - await c2.PublishAsync(message); - await Task.Delay(500); - Assert.AreEqual(1, receivedMessagesCount); + await c2.PublishAsync(message); + await Task.Delay(500); + Assert.AreEqual(1, receivedMessagesCount); - await Task.Delay(500); + await Task.Delay(500); - Assert.AreEqual(1, receivedMessagesCount); - } - } + Assert.AreEqual(1, receivedMessagesCount); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs b/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs index 24b370d15..8fd8fed5d 100644 --- a/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs @@ -6,87 +6,81 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Subscription_Identifier_Tests : BaseTestClass { - [TestClass] - public sealed class Subscription_Identifier_Tests : BaseTestClass + [TestMethod] + public async Task Server_Reports_Subscription_Identifiers_Supported() { - [TestMethod] - public async Task Server_Reports_Subscription_Identifiers_Supported() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + var client = testEnvironment.CreateClient(); + var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - Assert.IsTrue(connectResult.SubscriptionIdentifiersAvailable); - } - } + Assert.IsTrue(connectResult.SubscriptionIdentifiersAvailable); + } - [TestMethod] - public async Task Subscribe_With_Subscription_Identifier() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Subscribe_With_Subscription_Identifier() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); - var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").Build(); - var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build(); + var client1 = await testEnvironment.ConnectClient(); + var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").Build(); + var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build(); - await client1.SubscribeAsync(subscribeOptions); - await LongTestDelay(); + await client1.SubscribeAsync(subscribeOptions); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(0); + applicationMessageHandler.AssertReceivedCountEquals(0); - // The client will publish a message where it is itself subscribing to. - await client1.PublishStringAsync("Topic", "Payload", retain: true); - await LongTestDelay(); + // The client will publish a message where it is itself subscribing to. + await client1.PublishStringAsync("Topic", "Payload", retain: true); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(1); + applicationMessageHandler.AssertReceivedCountEquals(1); - applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(456); - } - } + Assert.IsTrue(applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(456)); + } - [TestMethod] - public async Task Subscribe_With_Multiple_Subscription_Identifiers() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Subscribe_With_Multiple_Subscription_Identifiers() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + var client1 = await testEnvironment.ConnectClient(); + var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); - var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic/A").Build(); - var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build(); - await client1.SubscribeAsync(subscribeOptions); + var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic/A").Build(); + var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build(); + await client1.SubscribeAsync(subscribeOptions); - await LongTestDelay(); + await LongTestDelay(); - topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic/+").Build(); - subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(789).WithTopicFilter(topicFilter).Build(); - await client1.SubscribeAsync(subscribeOptions); + topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic/+").Build(); + subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(789).WithTopicFilter(topicFilter).Build(); + await client1.SubscribeAsync(subscribeOptions); - await LongTestDelay(); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(0); + applicationMessageHandler.AssertReceivedCountEquals(0); - // The client will publish a message where it is itself subscribing to. - await client1.PublishStringAsync("Topic/A", "Payload", retain: true); - await LongTestDelay(); + // The client will publish a message where it is itself subscribing to. + await client1.PublishStringAsync("Topic/A", "Payload", retain: true); + await LongTestDelay(); - applicationMessageHandler.AssertReceivedCountEquals(1); + applicationMessageHandler.AssertReceivedCountEquals(1); - applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(456); - applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(789); - } - } + Assert.IsTrue(applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(456)); + Assert.IsTrue(applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(789)); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Subscription_TopicHash_Tests.cs b/Source/MQTTnet.Tests/Server/Subscription_TopicHash_Tests.cs index c067d5105..a022201db 100644 --- a/Source/MQTTnet.Tests/Server/Subscription_TopicHash_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Subscription_TopicHash_Tests.cs @@ -11,589 +11,588 @@ using MQTTnet.Server.Internal; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +[TestClass] +public sealed class SubscriptionTopicHashTests { - [TestClass] - public sealed class SubscriptionTopicHashTests + MqttSession _clientSession; + + /// + /// Even fairly regularly named topics as generated by the topic generator should result in shallow hash buckets + /// + /// + [TestMethod] + public void Check_Hash_Bucket_Depth() { - MqttSession _clientSession; - - /// - /// Even fairly regularly named topics as generated by the topic generator should result in shallow hash buckets - /// - /// - [TestMethod] - public void Check_Hash_Bucket_Depth() - { - const int numPublishers = 5000; - const int numTopicsPerPublisher = 10; + const int numPublishers = 5000; + const int numTopicsPerPublisher = 10; - TopicGenerator.Generate( - numPublishers, - numTopicsPerPublisher, - out var topicsByPublisher, - out _, - out _); + TopicGenerator.Generate( + numPublishers, + numTopicsPerPublisher, + out var topicsByPublisher, + out _, + out _); - // There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ... - // Hash bucket depths should remain low. - var bucketDepths = new Dictionary(); - var maxBucketDepth = 0; - ulong maxBucketDepthHash = 0; + // There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ... + // Hash bucket depths should remain low. + var bucketDepths = new Dictionary(); + var maxBucketDepth = 0; + ulong maxBucketDepthHash = 0; - var topicsByHash = new Dictionary>(); + var topicsByHash = new Dictionary>(); - foreach (var t in topicsByPublisher) + foreach (var t in topicsByPublisher) + { + var topics = t.Value; + foreach (var topic in topics) { - var topics = t.Value; - foreach (var topic in topics) - { - MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard); - - bucketDepths.TryGetValue(topicHash, out var currentValue); - ++currentValue; - bucketDepths[topicHash] = currentValue; + MqttTopicHash.Calculate(topic, out var topicHash, out _, out _); - if (currentValue > maxBucketDepth) - { - maxBucketDepth = currentValue; - maxBucketDepthHash = topicHash; - } + bucketDepths.TryGetValue(topicHash, out var currentValue); + ++currentValue; + bucketDepths[topicHash] = currentValue; - if (!topicsByHash.TryGetValue(topicHash, out var topicList)) - { - topicList = new List(); - topicsByHash.Add(topicHash, topicList); - } + if (currentValue > maxBucketDepth) + { + maxBucketDepth = currentValue; + maxBucketDepthHash = topicHash; + } - topicList.Add(topic); + if (!topicsByHash.TryGetValue(topicHash, out var topicList)) + { + topicList = []; + topicsByHash.Add(topicHash, topicList); } + + topicList.Add(topic); } + } - var maxDepthTopics = topicsByHash[maxBucketDepthHash]; + var maxDepthTopics = topicsByHash[maxBucketDepthHash]; - Console.Write("Max bucket depth is " + maxBucketDepth); + Console.Write("Max bucket depth is " + maxBucketDepth); - // for the test case the bucket depth should be less than 100 - Assert.IsTrue(maxBucketDepth < 100, "Unexpected high topic hash bucket depth"); - } + // for the test case the bucket depth should be less than 100 + Assert.IsTrue(maxBucketDepth < 100, "Unexpected high topic hash bucket depth"); + } - [TestMethod] - public void Check_Selected_Topic_Hashes() - { - CheckTopicHash("client1/building1/level1/sensor1", 0x655D4AF100000000, 0xFFFFFFFFFFFFFFFF); - CheckTopicHash("client1/building1/+/sensor1", 0x655D00F100000000, 0xFFFF00FFFFFFFFFF); - CheckTopicHash("client1/+/level1/+", 0x65004A0000000000, 0xFF00FF00FFFFFFFF); - CheckTopicHash("client1/building1/level1/#", 0x655D4A0000000000, 0xFFFFFF0000000000); - CheckTopicHash("client1/+/level1/#", 0x65004A0000000000, 0xFF00FF0000000000); - } + [TestMethod] + public void Check_Selected_Topic_Hashes() + { + CheckTopicHash("client1/building1/level1/sensor1", 0x655D4AF100000000, 0xFFFFFFFFFFFFFFFF); + CheckTopicHash("client1/building1/+/sensor1", 0x655D00F100000000, 0xFFFF00FFFFFFFFFF); + CheckTopicHash("client1/+/level1/+", 0x65004A0000000000, 0xFF00FF00FFFFFFFF); + CheckTopicHash("client1/building1/level1/#", 0x655D4A0000000000, 0xFFFFFF0000000000); + CheckTopicHash("client1/+/level1/#", 0x65004A0000000000, 0xFF00FF0000000000); + } - /// - /// Test long topic name with last level being # - /// - [TestMethod] - public void Match_Hash_Test_LongTopic_DetectMultiLevelWildcard() - { - var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#"; + /// + /// Test long topic name with last level being # + /// + [TestMethod] + public void Match_Hash_Test_LongTopic_DetectMultiLevelWildcard() + { + var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#"; - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + MqttTopicHash.Calculate(topic, out _, out _, out var topicHasWildcard); - Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); - } + Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); + } - /// - /// Test long topic name with last level being + - /// - [TestMethod] - public void Match_Hash_Test_LongTopic_DetectSingleLevelWildcard() - { - var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+"; + /// + /// Test long topic name with last level being + + /// + [TestMethod] + public void Match_Hash_Test_LongTopic_DetectSingleLevelWildcard() + { + var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+"; - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + MqttTopicHash.Calculate(topic, out _, out _, out var topicHasWildcard); - Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); - } + Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); + } - /// - /// Test long topic name with last level being # - /// - [TestMethod] - public void Match_Hash_Test_LongTopic_MultiWildcard() + /// + /// Test long topic name with last level being # + /// + [TestMethod] + public void Match_Hash_Test_LongTopic_MultiWildcard() + { + var sb = new StringBuilder(); + const int numLevels = 8; + var levelNames = new string[numLevels]; + for (var i = 0; i < numLevels; ++i) { - var sb = new StringBuilder(); - const int numLevels = 8; - var levelNames = new string[numLevels]; - for (var i = 0; i < numLevels; ++i) + if (i > 0) { - if (i > 0) - { - sb.Append("/"); - } - - string levelName; - if (i == numLevels - 1) - { - // last one is # - levelName = "#"; - } - else - { - levelName = "level" + i; - } + sb.Append('/'); + } - levelNames[i] = levelName; - sb.Append(levelName); + string levelName; + if (i == numLevels - 1) + { + // last one is # + levelName = "#"; + } + else + { + levelName = "level" + i; } - var topic = sb.ToString(); + levelNames[i] = levelName; + sb.Append(levelName); + } - // UInt64 is limited to 8 levels + var topic = sb.ToString(); - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + // UInt64 is limited to 8 levels - Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); + MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); - var hashBytes = GetBytes(topicHash); - // all bytes should contain checksum - var count = 0; - foreach (var h in hashBytes) - { - if (count < 7) - { - Assert.AreNotEqual(h, 0, "checksum mismatch"); - } - else - { - Assert.AreEqual(h, 0, "checksum mismatch"); - } + Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); - ++count; + var hashBytes = GetBytes(topicHash); + // all bytes should contain checksum + var count = 0; + foreach (var h in hashBytes) + { + if (count < 7) + { + Assert.AreNotEqual(h, 0, "checksum mismatch"); } - - // The mask should have ff except for last level - var hashMaskBytes = GetBytes(topicHashMask); - count = 0; - foreach (var h in hashMaskBytes) + else { - if (count < 7) - { - Assert.AreEqual(h, 0xff, "mask mismatch"); - } - else - { - Assert.AreEqual(h, 0, "last mask mismatch"); - } - - ++count; + Assert.AreEqual(h, 0, "checksum mismatch"); } + + ++count; } - /// - /// Test long topic name exceeding 8 levels - /// - [TestMethod] - public void Match_Hash_Test_LongTopic_NoWildCard() + // The mask should have ff except for last level + var hashMaskBytes = GetBytes(topicHashMask); + count = 0; + foreach (var h in hashMaskBytes) { - var sb = new StringBuilder(); - const int numLevels = 9; - var levelNames = new string[numLevels]; - for (var i = 0; i < numLevels; ++i) + if (count < 7) { - if (i > 0) - { - sb.Append("/"); - } + Assert.AreEqual(h, 0xff, "mask mismatch"); + } + else + { + Assert.AreEqual(h, 0, "last mask mismatch"); + } + + ++count; + } + } - var levelName = "level" + i; - levelNames[i] = levelName; - sb.Append(levelName); + /// + /// Test long topic name exceeding 8 levels + /// + [TestMethod] + public void Match_Hash_Test_LongTopic_NoWildCard() + { + var sb = new StringBuilder(); + const int numLevels = 9; + var levelNames = new string[numLevels]; + for (var i = 0; i < numLevels; ++i) + { + if (i > 0) + { + sb.Append('/'); } - var topic = sb.ToString(); + var levelName = "level" + i; + levelNames[i] = levelName; + sb.Append(levelName); + } - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + var topic = sb.ToString(); - Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present"); + MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present"); - var hashBytes = GetBytes(topicHash); - // all bytes should contain checksum - var count = 0; - foreach (var h in hashBytes) - { - Assert.AreNotEqual(h, 0, "checksum mismatch"); - ++count; - } - // The mask should have ff - var hashMaskBytes = GetBytes(topicHashMask); - count = 0; - foreach (var h in hashMaskBytes) - { - Assert.AreEqual(h, 0xff, "mask mismatch"); - ++count; - } + var hashBytes = GetBytes(topicHash); + // all bytes should contain checksum + var count = 0; + foreach (var h in hashBytes) + { + Assert.AreNotEqual(h, 0, "checksum mismatch"); + ++count; } - /// - /// Test long topic name with last level being + - /// - [TestMethod] - public void Match_Hash_Test_LongTopic_SingleWildCard() + // The mask should have ff + var hashMaskBytes = GetBytes(topicHashMask); + count = 0; + foreach (var h in hashMaskBytes) { - var sb = new StringBuilder(); - const int numLevels = 8; - var levelNames = new string[numLevels]; - for (var i = 0; i < numLevels; ++i) - { - if (i > 0) - { - sb.Append("/"); - } + Assert.AreEqual(h, 0xff, "mask mismatch"); + ++count; + } + } - string levelName; - if (i == numLevels - 1) - { - // last one is + - levelName = "+"; - } - else - { - levelName = "level" + i; - } + /// + /// Test long topic name with last level being + + /// + [TestMethod] + public void Match_Hash_Test_LongTopic_SingleWildCard() + { + var sb = new StringBuilder(); + const int numLevels = 8; + var levelNames = new string[numLevels]; + for (var i = 0; i < numLevels; ++i) + { + if (i > 0) + { + sb.Append('/'); + } - levelNames[i] = levelName; - sb.Append(levelName); + string levelName; + if (i == numLevels - 1) + { + // last one is + + levelName = "+"; + } + else + { + levelName = "level" + i; } - var topic = sb.ToString(); + levelNames[i] = levelName; + sb.Append(levelName); + } - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + var topic = sb.ToString(); - Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); + MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); - var hashBytes = GetBytes(topicHash); - // all bytes should contain checksum - var count = 0; - foreach (var h in hashBytes) - { - if (count < 7) - { - Assert.AreNotEqual(h, 0, "checksum mismatch"); - } - else - { - // wildcard position - Assert.AreEqual(h, 0, "checksum mismatch"); - } - ++count; + var hashBytes = GetBytes(topicHash); + // all bytes should contain checksum + var count = 0; + foreach (var h in hashBytes) + { + if (count < 7) + { + Assert.AreNotEqual(h, 0, "checksum mismatch"); } - - // The mask should have ff - var hashMaskBytes = GetBytes(topicHashMask); - count = 0; - foreach (var h in hashMaskBytes) + else { - if (count < 7) - { - Assert.AreEqual(h, 0xff, "mask mismatch"); - } - else - { - Assert.AreEqual(h, 0, "last mask mismatch"); - } - - ++count; + // wildcard position + Assert.AreEqual(h, 0, "checksum mismatch"); } - } - [TestMethod] - public void Match_Hash_Test_MultiWildCard() - { - var l0 = "pub0"; - var l1 = "topic1"; - var l2 = "sub1"; - var l3 = "#"; - var topic = $"{l0}/{l1}/{l2}/{l3}"; - - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); - - Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); - - var hashBytes = GetBytes(topicHash); - Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch"); - Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch"); - Assert.AreNotEqual(hashBytes[2], 0, "checksum 2 mismatch"); - Assert.AreEqual(hashBytes[3], 0, "checksum 3 mismatch"); - Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch"); - Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch"); - Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch"); - Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch"); - - // The mask should have zeroes where the wildcard is and zero onward - var hashMaskBytes = GetBytes(topicHashMask); - Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch"); - Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch"); - Assert.AreEqual(hashMaskBytes[2], 0xff, "mask 2 mismatch"); - Assert.AreEqual(hashMaskBytes[3], 0, "mask 3 mismatch"); - Assert.AreEqual(hashMaskBytes[4], 0, "mask 4 mismatch"); - Assert.AreEqual(hashMaskBytes[5], 0, "mask 5 mismatch"); - Assert.AreEqual(hashMaskBytes[6], 0, "mask 6 mismatch"); - Assert.AreEqual(hashMaskBytes[7], 0, "mask 7 mismatch"); + ++count; } - [TestMethod] - public void Match_Hash_Test_NoWildCard() + // The mask should have ff + var hashMaskBytes = GetBytes(topicHashMask); + count = 0; + foreach (var h in hashMaskBytes) { - var l0 = "pub0"; - var l1 = "topic1"; - var l2 = "sub1"; - var l3 = "prop1"; - var topic = $"{l0}/{l1}/{l2}/{l3}"; - - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); - - Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present"); - - - var hashBytes = GetBytes(topicHash); - Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch"); - Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch"); - Assert.AreNotEqual(hashBytes[2], 0, "checksum 2 mismatch"); - Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch"); - Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch"); - Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch"); - Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch"); - Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch"); - - // The mask should have ff - var hashMaskBytes = GetBytes(topicHashMask); - Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch"); - Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch"); - Assert.AreEqual(hashMaskBytes[2], 0xff, "mask 2 mismatch"); - Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch"); - Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch"); - Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch"); - Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch"); - Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch"); - } + if (count < 7) + { + Assert.AreEqual(h, 0xff, "mask mismatch"); + } + else + { + Assert.AreEqual(h, 0, "last mask mismatch"); + } - [TestMethod] - public async Task Match_Hash_Test_Search_MultiWildcard() - { - var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard); - var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard"); - // Should match all topics - Assert.AreEqual(topics.Count, matchCount, "Topics not matched"); + ++count; } + } - [TestMethod] - public async Task Match_Hash_Test_Search_NoWildcard() - { - var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.NoWildcard); + [TestMethod] + public void Match_Hash_Test_MultiWildCard() + { + var l0 = "pub0"; + var l1 = "topic1"; + var l2 = "sub1"; + var l3 = "#"; + var topic = $"{l0}/{l1}/{l2}/{l3}"; + + MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + + Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); + + var hashBytes = GetBytes(topicHash); + Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch"); + Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch"); + Assert.AreNotEqual(hashBytes[2], 0, "checksum 2 mismatch"); + Assert.AreEqual(hashBytes[3], 0, "checksum 3 mismatch"); + Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch"); + Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch"); + Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch"); + Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch"); + + // The mask should have zeroes where the wildcard is and zero onward + var hashMaskBytes = GetBytes(topicHashMask); + Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch"); + Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch"); + Assert.AreEqual(hashMaskBytes[2], 0xff, "mask 2 mismatch"); + Assert.AreEqual(hashMaskBytes[3], 0, "mask 3 mismatch"); + Assert.AreEqual(hashMaskBytes[4], 0, "mask 4 mismatch"); + Assert.AreEqual(hashMaskBytes[5], 0, "mask 5 mismatch"); + Assert.AreEqual(hashMaskBytes[6], 0, "mask 6 mismatch"); + Assert.AreEqual(hashMaskBytes[7], 0, "mask 7 mismatch"); + } - // all match lookup - var matchCount = CheckTopicSubscriptions(_clientSession, topics, "No Wildcard All Match"); + [TestMethod] + public void Match_Hash_Test_NoWildCard() + { + var l0 = "pub0"; + var l1 = "topic1"; + var l2 = "sub1"; + var l3 = "prop1"; + var topic = $"{l0}/{l1}/{l2}/{l3}"; + + MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + + Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present"); + + + var hashBytes = GetBytes(topicHash); + Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch"); + Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch"); + Assert.AreNotEqual(hashBytes[2], 0, "checksum 2 mismatch"); + Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch"); + Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch"); + Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch"); + Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch"); + Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch"); + + // The mask should have ff + var hashMaskBytes = GetBytes(topicHashMask); + Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch"); + Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch"); + Assert.AreEqual(hashMaskBytes[2], 0xff, "mask 2 mismatch"); + Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch"); + Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch"); + Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch"); + Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch"); + Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch"); + } - Assert.AreEqual(topics.Count, matchCount, "Not all topics matched"); + [TestMethod] + public async Task Match_Hash_Test_Search_MultiWildcard() + { + var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard); + var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard"); + // Should match all topics + Assert.AreEqual(topics.Count, matchCount, "Topics not matched"); + } - // no match lookup + [TestMethod] + public async Task Match_Hash_Test_Search_NoWildcard() + { + var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.NoWildcard); + + // all match lookup + var matchCount = CheckTopicSubscriptions(_clientSession, topics, "No Wildcard All Match"); + + Assert.AreEqual(topics.Count, matchCount, "Not all topics matched"); + + // no match lookup + { { + var topicsToFind = new List(); + foreach (var t in topics) { - var topicsToFind = new List(); - foreach (var t in topics) - { - topicsToFind.Add(t + "x"); - } + topicsToFind.Add(t + "x"); + } - matchCount = CheckTopicSubscriptions(_clientSession, topicsToFind, "No Wildcard No Match (Append X)"); + matchCount = CheckTopicSubscriptions(_clientSession, topicsToFind, "No Wildcard No Match (Append X)"); - Assert.AreEqual(0, matchCount, "Topic match count not zero"); - } + Assert.AreEqual(0, matchCount, "Topic match count not zero"); + } + { + var topicsToFind = new List(); + foreach (var t in topics) { - var topicsToFind = new List(); - foreach (var t in topics) - { - // replace last letter with x - topicsToFind.Add(t.Substring(0, t.Length - 1) + "x"); - } + // replace last letter with x + topicsToFind.Add(t.Substring(0, t.Length - 1) + "x"); + } - matchCount = CheckTopicSubscriptions(_clientSession, topicsToFind, "No Wildcard No Match (Replace X)"); + matchCount = CheckTopicSubscriptions(_clientSession, topicsToFind, "No Wildcard No Match (Replace X)"); - Assert.AreEqual(0, matchCount, "Topic match count not zero"); - } + Assert.AreEqual(0, matchCount, "Topic match count not zero"); } } + } - [TestMethod] - public async Task Match_Hash_Test_Search_SingleWildcard() - { - var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.SingleWildcard); - var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Single Wildcard"); - // Should match all topics - Assert.AreEqual(topics.Count, matchCount, "Topics not matched"); - } + [TestMethod] + public async Task Match_Hash_Test_Search_SingleWildcard() + { + var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.SingleWildcard); + var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Single Wildcard"); + // Should match all topics + Assert.AreEqual(topics.Count, matchCount, "Topics not matched"); + } - [TestMethod] - public void Match_Hash_Test_SingleWildCard() - { - var l0 = "pub0"; - var l1 = "topic1"; - var l2 = "+"; - var l3 = "prop1"; - var topic = $"{l0}/{l1}/{l2}/{l3}"; - - MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); - - Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); - - var hashBytes = GetBytes(topicHash); - Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch"); - Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch"); - Assert.AreEqual(hashBytes[2], 0, "checksum 2 mismatch"); - Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch"); - Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch"); - Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch"); - Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch"); - Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch"); - - // The mask should have zeroes where the wildcard and ff at the end - var hashMaskBytes = GetBytes(topicHashMask); - Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch"); - Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch"); - Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch"); - Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch"); - Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch"); - Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch"); - Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch"); - Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch"); - } + [TestMethod] + public void Match_Hash_Test_SingleWildCard() + { + var l0 = "pub0"; + var l1 = "topic1"; + var l2 = "+"; + var l3 = "prop1"; + var topic = $"{l0}/{l1}/{l2}/{l3}"; + + MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); + + Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); + + var hashBytes = GetBytes(topicHash); + Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch"); + Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch"); + Assert.AreEqual(hashBytes[2], 0, "checksum 2 mismatch"); + Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch"); + Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch"); + Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch"); + Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch"); + Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch"); + + // The mask should have zeroes where the wildcard and ff at the end + var hashMaskBytes = GetBytes(topicHashMask); + Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch"); + Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch"); + Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch"); + Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch"); + Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch"); + Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch"); + Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch"); + Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch"); + } - void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask) - { - MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard); + void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask) + { + MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out _); - Console.WriteLine(); - Console.WriteLine("Topic: " + topic); - Console.WriteLine($"Hash: {topicHash:X8}"); - Console.WriteLine($"Hash Mask: {hashMask:X8}"); + Console.WriteLine(); + Console.WriteLine("Topic: " + topic); + Console.WriteLine($"Hash: {topicHash:X8}"); + Console.WriteLine($"Hash Mask: {hashMask:X8}"); - Assert.AreEqual(expectedHash, topicHash, "Topic hash not as expected. Has the hash function changed?"); - Assert.AreEqual(expectedHashMask, hashMask, "Topic hash mask not as expected"); - } + Assert.AreEqual(expectedHash, topicHash, "Topic hash not as expected. Has the hash function changed?"); + Assert.AreEqual(expectedHashMask, hashMask, "Topic hash mask not as expected"); + } + + int CheckTopicSubscriptions(MqttSession clientSession, List topicsToFind, string subject) + { + var matchCount = 0; - int CheckTopicSubscriptions(MqttSession clientSession, List topicsToFind, string subject) { - var matchCount = 0; + var resultCount = 0; + var stopWatch = new Stopwatch(); + stopWatch.Start(); + var countUp = 0; + var countDown = topicsToFind.Count - 1; + for (; countUp < topicsToFind.Count; ++countUp, --countDown) { - var resultCount = 0; + var topicToFind = topicsToFind[countDown]; - var stopWatch = new Stopwatch(); - stopWatch.Start(); - var countUp = 0; - var countDown = topicsToFind.Count - 1; - for (; countUp < topicsToFind.Count; ++countUp, --countDown) - { - var topicToFind = topicsToFind[countDown]; + MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _); - MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _); - - clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result); - if (result.IsSubscribed) - { - ++matchCount; - } - - ++resultCount; + clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result); + if (result.IsSubscribed) + { + ++matchCount; } - stopWatch.Stop(); - - Console.Write("Match count: " + matchCount + "; "); - - Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds); + ++resultCount; } - return matchCount; - } + stopWatch.Stop(); - byte[] GetBytes(ulong value) - { - var bytes = BitConverter.GetBytes(value); - // Ensure that highest byte comes first for comparison left to right - if (BitConverter.IsLittleEndian) - { - return bytes.Reverse().ToArray(); - } + Console.Write("Match count: " + matchCount + "; "); - return bytes; + Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds); } + return matchCount; + } - async Task> PrepareTopicHashSubscriptions(TopicHashSelector selector) + byte[] GetBytes(ulong value) + { + var bytes = BitConverter.GetBytes(value); + // Ensure that highest byte comes first for comparison left to right + if (BitConverter.IsLittleEndian) { - const int numPublishers = 1; - const int numTopicsPerPublisher = 10000; - - TopicGenerator.Generate( - numPublishers, - numTopicsPerPublisher, - out var topicsByPublisher, - out var singleWildcardTopicsByPublisher, - out var multiWildcardTopicsByPublisher); - - var topics = topicsByPublisher.FirstOrDefault().Value; - var singleWildcardTopics = singleWildcardTopicsByPublisher.FirstOrDefault().Value; - var multiWildcardTopics = multiWildcardTopicsByPublisher.FirstOrDefault().Value; - - const string clientId = "Client1"; - var logger = new TestLogger(); - var serverOptions = new MqttServerOptions(); - var eventContainer = new MqttServerEventContainer(); - var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger); - var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger); - _clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary(), serverOptions, eventContainer, retainedMessagesManager, sessionManager); - - List topicsToSubscribe; - - switch (selector) - { - case TopicHashSelector.SingleWildcard: - topicsToSubscribe = singleWildcardTopics; - break; - case TopicHashSelector.MultiWildcard: - topicsToSubscribe = multiWildcardTopics; - break; - default: - topicsToSubscribe = topics; - break; - } + return bytes.Reverse().ToArray(); + } + + return bytes; + } - foreach (var t in topicsToSubscribe) - { - var subPacket = new MqttSubscribePacket(); - var filter = new MqttTopicFilter - { - Topic = t - }; - subPacket.TopicFilters.Add(filter); - await _clientSession.Subscribe(subPacket, default); - } - return topics; + async Task> PrepareTopicHashSubscriptions(TopicHashSelector selector) + { + const int numPublishers = 1; + const int numTopicsPerPublisher = 10000; + + TopicGenerator.Generate( + numPublishers, + numTopicsPerPublisher, + out var topicsByPublisher, + out var singleWildcardTopicsByPublisher, + out var multiWildcardTopicsByPublisher); + + var topics = topicsByPublisher.FirstOrDefault().Value; + var singleWildcardTopics = singleWildcardTopicsByPublisher.FirstOrDefault().Value; + var multiWildcardTopics = multiWildcardTopicsByPublisher.FirstOrDefault().Value; + + const string clientId = "Client1"; + var logger = new TestLogger(); + var serverOptions = new MqttServerOptions(); + var eventContainer = new MqttServerEventContainer(); + var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger); + var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger); + _clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary(), serverOptions, eventContainer, retainedMessagesManager, sessionManager); + + List topicsToSubscribe; + + switch (selector) + { + case TopicHashSelector.SingleWildcard: + topicsToSubscribe = singleWildcardTopics; + break; + case TopicHashSelector.MultiWildcard: + topicsToSubscribe = multiWildcardTopics; + break; + default: + topicsToSubscribe = topics; + break; } - enum TopicHashSelector + foreach (var t in topicsToSubscribe) { - NoWildcard, - SingleWildcard, - MultiWildcard + var subPacket = new MqttSubscribePacket(); + var filter = new MqttTopicFilter + { + Topic = t + }; + subPacket.TopicFilters.Add(filter); + await _clientSession.Subscribe(subPacket, default); } + + return topics; + } + + enum TopicHashSelector + { + NoWildcard, + SingleWildcard, + MultiWildcard } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Tls_Tests.cs b/Source/MQTTnet.Tests/Server/Tls_Tests.cs index a87b0dc4c..b81657bd5 100644 --- a/Source/MQTTnet.Tests/Server/Tls_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Tls_Tests.cs @@ -12,188 +12,184 @@ using MQTTnet.Server; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Tls_Tests : BaseTestClass { - [TestClass] - public sealed class Tls_Tests : BaseTestClass + static X509Certificate2 CreateCertificate(string oid) { - static X509Certificate2 CreateCertificate(string oid) - { - var sanBuilder = new SubjectAlternativeNameBuilder(); - sanBuilder.AddIpAddress(IPAddress.Loopback); - sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); - sanBuilder.AddDnsName("localhost"); + var sanBuilder = new SubjectAlternativeNameBuilder(); + sanBuilder.AddIpAddress(IPAddress.Loopback); + sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); + sanBuilder.AddDnsName("localhost"); - using (var rsa = RSA.Create()) - { - var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); + using var rsa = RSA.Create(); + var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); - certRequest.CertificateExtensions.Add( - new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); + certRequest.CertificateExtensions.Add( + new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); - certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false)); + certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false)); - certRequest.CertificateExtensions.Add(sanBuilder.Build()); + certRequest.CertificateExtensions.Add(sanBuilder.Build()); - using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10))) - { - var pfxCertificate = new X509Certificate2( - certificate.Export(X509ContentType.Pfx), - (string)null, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); - - return pfxCertificate; - } - } - } + using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)); + var pfxCertificate = new X509Certificate2( + certificate.Export(X509ContentType.Pfx), + (string)null, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); - [TestMethod] - public async Task Tls_Swap_Test() - { - var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); - var serverOptionsBuilder = testEnvironment.ServerFactory.CreateServerOptionsBuilder(); + return pfxCertificate; + } - var firstOid = "1.3.6.1.5.5.7.3.1"; - var secondOid = "1.3.6.1.5.5.7.3.2"; + [TestMethod] + public async Task Tls_Swap_Test() + { + var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var serverOptionsBuilder = testEnvironment.ServerFactory.CreateServerOptionsBuilder(); - var certificateProvider = new CertificateProvider - { - CurrentCertificate = CreateCertificate(firstOid) - }; + var firstOid = "1.3.6.1.5.5.7.3.1"; + var secondOid = "1.3.6.1.5.5.7.3.2"; - serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider); + var certificateProvider = new CertificateProvider + { + CurrentCertificate = CreateCertificate(firstOid) + }; - var serverOptions = serverOptionsBuilder.Build(); + serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider); - var server = testEnvironment.CreateServer(serverOptions); + var serverOptions = serverOptionsBuilder.Build(); - var publishedCount = 0; - server.InterceptingPublishAsync += args => - { - Interlocked.Increment(ref publishedCount); + var server = testEnvironment.CreateServer(serverOptions); - return Task.CompletedTask; - }; + var publishedCount = 0; + server.InterceptingPublishAsync += _ => + { + Interlocked.Increment(ref publishedCount); - await server.StartAsync(); + return Task.CompletedTask; + }; - var firstClient = await ConnectClientAsync( - testEnvironment, - args => - { - Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType().First().EnhancedKeyUsages[0].Value); - return true; - }); + await server.StartAsync(); - var firstClientReceivedCount = 0; - firstClient.ApplicationMessageReceivedAsync += args => + var firstClient = await ConnectClientAsync( + testEnvironment, + args => { - Interlocked.Increment(ref firstClientReceivedCount); + Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType().First().EnhancedKeyUsages[0].Value); + return true; + }); + + var firstClientReceivedCount = 0; + firstClient.ApplicationMessageReceivedAsync += _ => + { + Interlocked.Increment(ref firstClientReceivedCount); + + return Task.CompletedTask; + }; - return Task.CompletedTask; - }; + await firstClient.SubscribeAsync("TestTopic1"); - await firstClient.SubscribeAsync("TestTopic1"); + await firstClient.PublishAsync( + new MqttApplicationMessage + { + Topic = "TestTopic1", + PayloadSegment = new ArraySegment([1, 2, 3, 4]) + }); - await firstClient.PublishAsync( + await testEnvironment.Server.InjectApplicationMessage( + new InjectedMqttApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) - }); + PayloadSegment = new ArraySegment([1, 2, 3, 4]) + })); - await testEnvironment.Server.InjectApplicationMessage( - new InjectedMqttApplicationMessage( - new MqttApplicationMessage - { - Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) - })); + certificateProvider.CurrentCertificate = CreateCertificate(secondOid); - certificateProvider.CurrentCertificate = CreateCertificate(secondOid); + // Validate that the certificate was switched + var secondClient = await ConnectClientAsync( + testEnvironment, + args => + { + Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType().First().EnhancedKeyUsages[0].Value); + return true; + }); - // Validate that the certificate was switched - var secondClient = await ConnectClientAsync( - testEnvironment, - args => - { - Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType().First().EnhancedKeyUsages[0].Value); - return true; - }); + var secondClientReceivedCount = 0; + secondClient.ApplicationMessageReceivedAsync += _ => + { + Interlocked.Increment(ref secondClientReceivedCount); - var secondClientReceivedCount = 0; - secondClient.ApplicationMessageReceivedAsync += args => - { - Interlocked.Increment(ref secondClientReceivedCount); + return Task.CompletedTask; + }; - return Task.CompletedTask; - }; + await secondClient.SubscribeAsync("TestTopic2"); - await secondClient.SubscribeAsync("TestTopic2"); + await firstClient.PublishAsync( + new MqttApplicationMessage + { + Topic = "TestTopic2", + PayloadSegment = new ArraySegment([1, 2, 3, 4]) + }); - await firstClient.PublishAsync( + await testEnvironment.Server.InjectApplicationMessage( + new InjectedMqttApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic2", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) - }); + PayloadSegment = new ArraySegment([1, 2, 3, 4]) + })); - await testEnvironment.Server.InjectApplicationMessage( - new InjectedMqttApplicationMessage( - new MqttApplicationMessage - { - Topic = "TestTopic2", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) - })); + // Ensure first client still works + await firstClient.PublishAsync( + new MqttApplicationMessage + { + Topic = "TestTopic1", + PayloadSegment = new ArraySegment([1, 2, 3, 4]) + }); - // Ensure first client still works - await firstClient.PublishAsync( + await testEnvironment.Server.InjectApplicationMessage( + new InjectedMqttApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) - }); + PayloadSegment = new ArraySegment([1, 2, 3, 4]) + })); - await testEnvironment.Server.InjectApplicationMessage( - new InjectedMqttApplicationMessage( - new MqttApplicationMessage - { - Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) - })); + await Task.Delay(1000); - await Task.Delay(1000); + Assert.AreEqual(6, publishedCount); + Assert.AreEqual(4, firstClientReceivedCount); + Assert.AreEqual(2, secondClientReceivedCount); - Assert.AreEqual(6, publishedCount); - Assert.AreEqual(4, firstClientReceivedCount); - Assert.AreEqual(2, secondClientReceivedCount); + await server.StopAsync().ConfigureAwait(false); + } - await server.StopAsync(); - } + static async Task ConnectClientAsync(TestEnvironment testEnvironment, Func certValidator) + { + var clientOptionsBuilder = testEnvironment.ClientFactory.CreateClientOptionsBuilder(); + clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString()) + .WithTcpServer("localhost", 8883) + .WithTlsOptions( + o => + { + o.WithSslProtocols(SslProtocols.Tls12).WithCertificateValidationHandler(certValidator); + }); - static async Task ConnectClientAsync(TestEnvironment testEnvironment, Func certValidator) - { - var clientOptionsBuilder = testEnvironment.ClientFactory.CreateClientOptionsBuilder(); - clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString()) - .WithTcpServer("localhost", 8883) - .WithTlsOptions( - o => - { - o.WithSslProtocols(SslProtocols.Tls12).WithCertificateValidationHandler(certValidator); - }); - - var clientOptions = clientOptionsBuilder.Build(); - return await testEnvironment.ConnectClient(clientOptions); - } + var clientOptions = clientOptionsBuilder.Build(); + return await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); + } - sealed class CertificateProvider : ICertificateProvider - { - public X509Certificate2 CurrentCertificate { get; set; } + sealed class CertificateProvider : ICertificateProvider + { + public X509Certificate2 CurrentCertificate { get; set; } - public X509Certificate2 GetCertificate() - { - return CurrentCertificate; - } + public X509Certificate2 GetCertificate() + { + return CurrentCertificate; } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Topic_Alias_Tests.cs b/Source/MQTTnet.Tests/Server/Topic_Alias_Tests.cs index 024fca16c..41a42daf4 100644 --- a/Source/MQTTnet.Tests/Server/Topic_Alias_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Topic_Alias_Tests.cs @@ -9,72 +9,68 @@ using MQTTnet.Formatter; using MQTTnet.Internal; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Topic_Alias_Tests : BaseTestClass { - [TestClass] - public sealed class Topic_Alias_Tests : BaseTestClass + [TestMethod] + public async Task Server_Reports_Topic_Alias_Supported() { - [TestMethod] - public async Task Server_Reports_Topic_Alias_Supported() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var client = testEnvironment.CreateClient(); + var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .Build()); + var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .Build()); - Assert.AreEqual(connectResult.TopicAliasMaximum, ushort.MaxValue); - } - } + Assert.AreEqual(connectResult.TopicAliasMaximum, ushort.MaxValue); + } - [TestMethod] - public async Task Publish_With_Topic_Alias() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + [TestMethod] + public async Task Publish_With_Topic_Alias() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var receivedTopics = new List(); + var receivedTopics = new List(); - var c1 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); - c1.ApplicationMessageReceivedAsync += e => - { - lock (receivedTopics) - { - receivedTopics.Add(e.ApplicationMessage.Topic); - } + var c1 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); + c1.ApplicationMessageReceivedAsync += e => + { + lock (receivedTopics) + { + receivedTopics.Add(e.ApplicationMessage.Topic); + } - return CompletedTask.Instance; - }; + return CompletedTask.Instance; + }; - await c1.SubscribeAsync("#"); + await c1.SubscribeAsync("#"); - var c2 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); + var c2 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); - var message = new MqttApplicationMessage - { - Topic = "this_is_the_topic", - TopicAlias = 22 - }; + var message = new MqttApplicationMessage + { + Topic = "this_is_the_topic", + TopicAlias = 22 + }; - await c2.PublishAsync(message); + await c2.PublishAsync(message); - message.Topic = null; + message.Topic = null; - await c2.PublishAsync(message); - await c2.PublishAsync(message); + await c2.PublishAsync(message); + await c2.PublishAsync(message); - await Task.Delay(500); + await Task.Delay(500); - Assert.AreEqual(3, receivedTopics.Count); - CollectionAssert.AllItemsAreNotNull(receivedTopics); - Assert.IsTrue(receivedTopics.All(t => t.Equals("this_is_the_topic"))); - } - } + Assert.AreEqual(3, receivedTopics.Count); + CollectionAssert.AllItemsAreNotNull(receivedTopics); + Assert.IsTrue(receivedTopics.All(t => t.Equals("this_is_the_topic"))); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Unsubscribe_Tests.cs b/Source/MQTTnet.Tests/Server/Unsubscribe_Tests.cs index 9f97ad88e..01ef9ecd6 100644 --- a/Source/MQTTnet.Tests/Server/Unsubscribe_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Unsubscribe_Tests.cs @@ -8,52 +8,47 @@ using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Internal; -using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Unsubscribe_Tests : BaseTestClass { - [TestClass] - public sealed class Unsubscribe_Tests : BaseTestClass + [TestMethod] + [ExpectedException(typeof(MqttClientDisconnectedException))] + public async Task Disconnect_While_Unsubscribing() { - [TestMethod] - [ExpectedException(typeof(MqttClientDisconnectedException))] - public async Task Disconnect_While_Unsubscribing() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer(); + + // The client will be disconnected directly after subscribing! + server.ClientUnsubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId); - // The client will be disconnect directly after subscribing! - server.ClientUnsubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection); + var client = await testEnvironment.ConnectClient(); + await client.SubscribeAsync("#"); + await client.UnsubscribeAsync("#"); + } - var client = await testEnvironment.ConnectClient(); - await client.SubscribeAsync("#"); - await client.UnsubscribeAsync("#"); - } - } + [TestMethod] + public async Task Intercept_Unsubscribe_With_User_Properties() + { + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + var server = await testEnvironment.StartServer(); - [TestMethod] - public async Task Intercept_Unsubscribe_With_User_Properties() + InterceptingUnsubscriptionEventArgs eventArgs = null; + server.InterceptingUnsubscriptionAsync += e => { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - var server = await testEnvironment.StartServer(); - - InterceptingUnsubscriptionEventArgs eventArgs = null; - server.InterceptingUnsubscriptionAsync += e => - { - eventArgs = e; - return CompletedTask.Instance; - }; + eventArgs = e; + return CompletedTask.Instance; + }; - var client = await testEnvironment.ConnectClient(); + var client = await testEnvironment.ConnectClient(); - var unsubscribeOptions = testEnvironment.ClientFactory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build(); - await client.UnsubscribeAsync(unsubscribeOptions); + var unsubscribeOptions = testEnvironment.ClientFactory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build(); + await client.UnsubscribeAsync(unsubscribeOptions); - CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties); - } - } + CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/User_Properties_Tests.cs b/Source/MQTTnet.Tests/Server/User_Properties_Tests.cs index 2a6f07137..a84849a94 100644 --- a/Source/MQTTnet.Tests/Server/User_Properties_Tests.cs +++ b/Source/MQTTnet.Tests/Server/User_Properties_Tests.cs @@ -2,64 +2,56 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -using MQTTnet.Implementations; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Server -{ - [TestClass] - public class Feature_Tests - { - public TestContext TestContext { get; set; } +namespace MQTTnet.Tests.Server; - [TestMethod] - public async Task Use_User_Properties() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServer(); +// ReSharper disable InconsistentNaming +[TestClass] +public class Feature_Tests +{ + public TestContext TestContext { get; set; } - var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + [TestMethod] + public async Task Use_User_Properties() + { + using var testEnvironment = new TestEnvironment(TestContext); + await testEnvironment.StartServer(); - var message = new MqttApplicationMessageBuilder() - .WithTopic("A") - .WithUserProperty("x", "1") - .WithUserProperty("y", "2") - .WithUserProperty("z", "3") - .WithUserProperty("z", "4"); // z is here two times to test list of items + var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - await receiver.SubscribeAsync(new MqttClientSubscribeOptions - { - TopicFilters = new List - { - new MqttTopicFilter { Topic = "#" } - } - }, CancellationToken.None); + var message = new MqttApplicationMessageBuilder() + .WithTopic("A") + .WithUserProperty("x", "1") + .WithUserProperty("y", "2") + .WithUserProperty("z", "3") + .WithUserProperty("z", "4"); // z is here two times to test list of items - MqttApplicationMessage receivedMessage = null; - receiver.ApplicationMessageReceivedAsync += e => - { - receivedMessage = e.ApplicationMessage; - return CompletedTask.Instance; - }; + await receiver.SubscribeAsync(new MqttClientSubscribeOptions + { + TopicFilters = [new MqttTopicFilter { Topic = "#" }] + }, CancellationToken.None); - await sender.PublishAsync(message.Build(), CancellationToken.None); + MqttApplicationMessage receivedMessage = null; + receiver.ApplicationMessageReceivedAsync += e => + { + receivedMessage = e.ApplicationMessage; + return CompletedTask.Instance; + }; - await Task.Delay(1000); + await sender.PublishAsync(message.Build(), CancellationToken.None); - Assert.IsNotNull(receivedMessage); - Assert.AreEqual("A", receivedMessage.Topic); - Assert.AreEqual(4, receivedMessage.UserProperties.Count); + await Task.Delay(1000); - } - } + Assert.IsNotNull(receivedMessage); + Assert.AreEqual("A", receivedMessage.Topic); + Assert.AreEqual(4, receivedMessage.UserProperties.Count); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Wildcard_Subscription_Available_Tests.cs b/Source/MQTTnet.Tests/Server/Wildcard_Subscription_Available_Tests.cs index 85b224d63..8f90f2ef3 100644 --- a/Source/MQTTnet.Tests/Server/Wildcard_Subscription_Available_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Wildcard_Subscription_Available_Tests.cs @@ -6,41 +6,37 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Wildcard_Subscription_Available_Tests : BaseTestClass { - [TestClass] - public sealed class Wildcard_Subscription_Available_Tests : BaseTestClass + [TestMethod] + public async Task Server_Reports_Wildcard_Subscription_Available_Tests_Supported_V3() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); + + var client = testEnvironment.CreateClient(); + var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V311) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + + Assert.IsTrue(connectResult.WildcardSubscriptionAvailable); + } + + [TestMethod] + public async Task Server_Reports_Wildcard_Subscription_Available_Tests_Supported_V5() { - [TestMethod] - public async Task Server_Reports_Wildcard_Subscription_Available_Tests_Supported_V3() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); - - var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V311) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - - Assert.IsTrue(connectResult.WildcardSubscriptionAvailable); - } - } - - [TestMethod] - public async Task Server_Reports_Wildcard_Subscription_Available_Tests_Supported_V5() - { - using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) - { - await testEnvironment.StartServer(); - - var client = testEnvironment.CreateClient(); - var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); - - Assert.IsTrue(connectResult.WildcardSubscriptionAvailable); - } - } + using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500); + await testEnvironment.StartServer(); + + var client = testEnvironment.CreateClient(); + var connectResult = await client.ConnectAsync(testEnvironment.ClientFactory.CreateClientOptionsBuilder() + .WithProtocolVersion(MqttProtocolVersion.V500) + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()); + + Assert.IsTrue(connectResult.WildcardSubscriptionAvailable); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Server/Will_Tests.cs b/Source/MQTTnet.Tests/Server/Will_Tests.cs index 0e823bcde..e4e96ce0a 100644 --- a/Source/MQTTnet.Tests/Server/Will_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Will_Tests.cs @@ -3,78 +3,72 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class Will_Tests : BaseTestClass { - [TestClass] - public sealed class Will_Tests : BaseTestClass + [TestMethod] + public async Task Intercept_Will_Message() { - [TestMethod] - public async Task Intercept_Will_Message() - { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer().ConfigureAwait(false); - - MqttApplicationMessage willMessage = null; - server.InterceptingPublishAsync += eventArgs => - { - willMessage = eventArgs.ApplicationMessage; - return CompletedTask.Instance; - }; - - await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false); - var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce); - var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); - takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. - - await LongTestDelay().ConfigureAwait(false); - - Assert.IsNotNull(willMessage); - } - } - - [TestMethod] - public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect() + using var testEnvironment = CreateTestEnvironment(); + var server = await testEnvironment.StartServer().ConfigureAwait(false); + + MqttApplicationMessage willMessage = null; + server.InterceptingPublishAsync += eventArgs => { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + willMessage = eventArgs.ApplicationMessage; + return CompletedTask.Instance; + }; - var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false); + await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false); + var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce); + var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); + takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + await LongTestDelay().ConfigureAwait(false); - await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); + Assert.IsNotNull(willMessage); + } - var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will"); - var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); - await sender.DisconnectAsync().ConfigureAwait(false); + [TestMethod] + public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - await LongTestDelay().ConfigureAwait(false); + var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false); - Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count); - } - } + var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); - [TestMethod] - public async Task Will_Message_Send() - { - using (var testEnvironment = CreateTestEnvironment()) - { - await testEnvironment.StartServer(); + await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); + + var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will"); + var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); + await sender.DisconnectAsync().ConfigureAwait(false); + + await LongTestDelay().ConfigureAwait(false); + + Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count); + } + + [TestMethod] + public async Task Will_Message_Send() + { + using var testEnvironment = CreateTestEnvironment(); + await testEnvironment.StartServer(); - var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); - await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); + var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false); + var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); - var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce); - var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); - takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. + var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce); + var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); + takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. - await LongTestDelay().ConfigureAwait(false); + await LongTestDelay().ConfigureAwait(false); - Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count); - } - } + Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count); } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs b/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs index 61f8987cd..d7d6f7c2f 100644 --- a/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs +++ b/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs @@ -3,162 +3,161 @@ // See the LICENSE file in the project root for more information. using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Server; using MQTTnet.Server.Internal; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +// ReSharper disable InconsistentNaming +[TestClass] +public sealed class MqttTopicFilterComparer_Tests { - [TestClass] - public sealed class MqttTopicFilterComparer_Tests + [TestMethod] + public void AllLevelsWildcardMatch() { - [TestMethod] - public void AllLevelsWildcardMatch() - { - CompareAndAssert("A/B/C/D", "#", MqttTopicFilterCompareResult.IsMatch); - } + CompareAndAssert("A/B/C/D", "#", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void BeginningOneLevelWildcardMatch() - { - CompareAndAssert("A/B/C", "+/B/C", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void BeginningOneLevelWildcardMatch() + { + CompareAndAssert("A/B/C", "+/B/C", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void Compare_UTF8_String_Match() - { - CompareAndAssert("öäüß", "öäüß", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void Compare_UTF8_String_Match() + { + CompareAndAssert("öäüß", "öäüß", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void Compare_UTF8_String_No_Match() - { - CompareAndAssert("ae", "ä", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void Compare_UTF8_String_No_Match() + { + CompareAndAssert("ae", "ä", MqttTopicFilterCompareResult.NoMatch); + } - [TestMethod] - public void DirectMatch() - { - CompareAndAssert("A/B/C", "A/B/C", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void DirectMatch() + { + CompareAndAssert("A/B/C", "A/B/C", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void DirectNoMatch() - { - CompareAndAssert("A/B/X", "A/B/C", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void DirectNoMatch() + { + CompareAndAssert("A/B/X", "A/B/C", MqttTopicFilterCompareResult.NoMatch); + } - [TestMethod] - public void EndMultipleLevelsWildcardMatch() - { - CompareAndAssert("A/B/C", "A/#", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void EndMultipleLevelsWildcardMatch() + { + CompareAndAssert("A/B/C", "A/#", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void EndMultipleLevelsWildcardMatchEmptyLevel() - { - CompareAndAssert("A/", "A/#", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void EndMultipleLevelsWildcardMatchEmptyLevel() + { + CompareAndAssert("A/", "A/#", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void EndMultipleLevelsWildcardNoMatch() - { - CompareAndAssert("A/B/C/D", "A/C/#", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void EndMultipleLevelsWildcardNoMatch() + { + CompareAndAssert("A/B/C/D", "A/C/#", MqttTopicFilterCompareResult.NoMatch); + } - [TestMethod] - public void EndOneLevelWildcardMatch() - { - CompareAndAssert("A/B/C", "A/B/+", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void EndOneLevelWildcardMatch() + { + CompareAndAssert("A/B/C", "A/B/+", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void Hash_Match_With_Separator_Only() - { - //A Topic Name or Topic Filter consisting only of the ‘/’ character is valid - CompareAndAssert("/", "#", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void Hash_Match_With_Separator_Only() + { + //A Topic Name or Topic Filter consisting only of the ‘/’ character is valid + CompareAndAssert("/", "#", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void MiddleOneLevelWildcardMatch() - { - CompareAndAssert("A/B/C", "A/+/C", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void MiddleOneLevelWildcardMatch() + { + CompareAndAssert("A/B/C", "A/+/C", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void MiddleOneLevelWildcardNoMatch() - { - CompareAndAssert("A/B/C/D", "A/+/C", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void MiddleOneLevelWildcardNoMatch() + { + CompareAndAssert("A/B/C/D", "A/+/C", MqttTopicFilterCompareResult.NoMatch); + } - [TestMethod] - public void MultiLevel_Sport() - { - // Tests from official MQTT spec (4.7.1.2 Multi-level wildcard) - CompareAndAssert("sport/tennis/player1", "sport/tennis/player1/#", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/player1/#", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("sport/tennis/player1/score/wimbledon", "sport/tennis/player1/#", MqttTopicFilterCompareResult.IsMatch); - - CompareAndAssert("sport/tennis/player1", "sport/tennis/+", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("sport/tennis/player2", "sport/tennis/+", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/+", MqttTopicFilterCompareResult.NoMatch); - - CompareAndAssert("sport", "sport/#", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("sport", "sport/+", MqttTopicFilterCompareResult.NoMatch); - CompareAndAssert("sport/", "sport/+", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void MultiLevel_Sport() + { + // Tests from official MQTT spec (4.7.1.2 Multi-level wildcard) + CompareAndAssert("sport/tennis/player1", "sport/tennis/player1/#", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/player1/#", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("sport/tennis/player1/score/wimbledon", "sport/tennis/player1/#", MqttTopicFilterCompareResult.IsMatch); + + CompareAndAssert("sport/tennis/player1", "sport/tennis/+", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("sport/tennis/player2", "sport/tennis/+", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/+", MqttTopicFilterCompareResult.NoMatch); + + CompareAndAssert("sport", "sport/#", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("sport", "sport/+", MqttTopicFilterCompareResult.NoMatch); + CompareAndAssert("sport/", "sport/+", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void Plus_Match_With_Separator_Only() - { - //A Topic Name or Topic Filter consisting only of the ‘/’ character is valid - CompareAndAssert("A", "+", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void Plus_Match_With_Separator_Only() + { + //A Topic Name or Topic Filter consisting only of the ‘/’ character is valid + CompareAndAssert("A", "+", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void Reserved_Multi_Level_Wildcard_Only() - { - CompareAndAssert("$SPECIAL/TOPIC", "#", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void Reserved_Multi_Level_Wildcard_Only() + { + CompareAndAssert("$SPECIAL/TOPIC", "#", MqttTopicFilterCompareResult.NoMatch); + } - [TestMethod] - public void Reserved_Single_Level_Wildcard() - { - CompareAndAssert("$SYS/monitor/Clients", "$SYS/#", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void Reserved_Single_Level_Wildcard() + { + CompareAndAssert("$SYS/monitor/Clients", "$SYS/#", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void Reserved_Single_Level_Wildcard_Prefix() - { - CompareAndAssert("$SYS/monitor/Clients", "+/monitor/Clients", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void Reserved_Single_Level_Wildcard_Prefix() + { + CompareAndAssert("$SYS/monitor/Clients", "+/monitor/Clients", MqttTopicFilterCompareResult.NoMatch); + } - [TestMethod] - public void Reserved_Single_Level_Wildcard_Suffix() - { - CompareAndAssert("$SYS/monitor/Clients", "$SYS/monitor/+", MqttTopicFilterCompareResult.IsMatch); - } + [TestMethod] + public void Reserved_Single_Level_Wildcard_Suffix() + { + CompareAndAssert("$SYS/monitor/Clients", "$SYS/monitor/+", MqttTopicFilterCompareResult.IsMatch); + } - [TestMethod] - public void SingleLevel_Finance() - { - // Tests from official MQTT spec (4.7.1.3 Single level wildcard) - CompareAndAssert("/finance", "+/+", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("/finance", "/+", MqttTopicFilterCompareResult.IsMatch); - CompareAndAssert("/finance", "+", MqttTopicFilterCompareResult.NoMatch); - } + [TestMethod] + public void SingleLevel_Finance() + { + // Tests from official MQTT spec (4.7.1.3 Single level wildcard) + CompareAndAssert("/finance", "+/+", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("/finance", "/+", MqttTopicFilterCompareResult.IsMatch); + CompareAndAssert("/finance", "+", MqttTopicFilterCompareResult.NoMatch); + } - static void CompareAndAssert(string topic, string filter, MqttTopicFilterCompareResult expectedResult) - { - Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter)); + static void CompareAndAssert(string topic, string filter, MqttTopicFilterCompareResult expectedResult) + { + Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter)); - MqttTopicHash.Calculate(topic, out var topicHash, out _, out _); - MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _); + MqttTopicHash.Calculate(topic, out var topicHash, out _, out _); + MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _); - if (expectedResult == MqttTopicFilterCompareResult.IsMatch) - { - // If it matches then the hash evaluation should also indicate a match - Assert.IsTrue((topicHash & filterTopicHashMask) == filterTopicHash, "Incorrect topic hash (is equal)"); - } + if (expectedResult == MqttTopicFilterCompareResult.IsMatch) + { + // If it matches then the hash evaluation should also indicate a match + Assert.IsTrue((topicHash & filterTopicHashMask) == filterTopicHash, "Incorrect topic hash (is equal)"); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/TopicGenerator.cs b/Source/MQTTnet.Tests/TopicGenerator.cs index d623d107f..ee413ab7c 100644 --- a/Source/MQTTnet.Tests/TopicGenerator.cs +++ b/Source/MQTTnet.Tests/TopicGenerator.cs @@ -2,92 +2,91 @@ using System.Collections.Generic; using MQTTnet.Extensions.TopicTemplate; -namespace MQTTnet.Tests +namespace MQTTnet.Tests; + +public class TopicGenerator { - public class TopicGenerator + public static void Generate( + int numPublishers, int numTopicsPerPublisher, + out Dictionary> topicsByPublisher, + out Dictionary> singleWildcardTopicsByPublisher, + out Dictionary> multiWildcardTopicsByPublisher + ) { - public static void Generate( - int numPublishers, int numTopicsPerPublisher, - out Dictionary> topicsByPublisher, - out Dictionary> singleWildcardTopicsByPublisher, - out Dictionary> multiWildcardTopicsByPublisher - ) + topicsByPublisher = new Dictionary>(); + singleWildcardTopicsByPublisher = new Dictionary>(); + multiWildcardTopicsByPublisher = new Dictionary>(); + + // Find some reasonable distribution across three topic levels + var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0)); + if (topicsPerLevel <= 0) { - topicsByPublisher = new Dictionary>(); - singleWildcardTopicsByPublisher = new Dictionary>(); - multiWildcardTopicsByPublisher = new Dictionary>(); + topicsPerLevel = 1; + } - // Find some reasonable distribution across three topic levels - var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0)); - if (topicsPerLevel <= 0) - { - topicsPerLevel = 1; - } + int numLevel1Topics = topicsPerLevel; + int numLevel2Topics = topicsPerLevel; - int numLevel1Topics = topicsPerLevel; - int numLevel2Topics = topicsPerLevel; + var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); + if (maxNumLevel3Topics <= 0) + { + maxNumLevel3Topics = 1; + } - var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); - if (maxNumLevel3Topics <= 0) - { - maxNumLevel3Topics = 1; - } - - MqttTopicTemplate baseTemplate = new MqttTopicTemplate("{publisher}/{building}/{level}/{sensor}"); + MqttTopicTemplate baseTemplate = new MqttTopicTemplate("{publisher}/{building}/{level}/{sensor}"); + + for (var p = 0; p < numPublishers; ++p) + { + int publisherTopicCount = 0; + var publisherName = "pub" + p; + var publisherTemplate = baseTemplate + .WithParameter("publisher", publisherName); - for (var p = 0; p < numPublishers; ++p) + for (var l1 = 0; l1 < numLevel1Topics; ++l1) { - int publisherTopicCount = 0; - var publisherName = "pub" + p; - var publisherTemplate = baseTemplate - .WithParameter("publisher", publisherName); - - for (var l1 = 0; l1 < numLevel1Topics; ++l1) + var l1Template = publisherTemplate.WithParameter("building", "building" + (l1 + 1)); + for (var l2 = 0; l2 < numLevel2Topics; ++l2) { - var l1Template = publisherTemplate.WithParameter("building", "building" + (l1 + 1)); - for (var l2 = 0; l2 < numLevel2Topics; ++l2) + var l2Template = l1Template.WithParameter("level", "level" + (l2 + 1)); + for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) { - var l2Template = l1Template.WithParameter("level", "level" + (l2 + 1)); - for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) - { - if (publisherTopicCount >= numTopicsPerPublisher) - break; - - var l3Template = l2Template - .WithParameter("sensor", "sensor" + (l3 + 1)); - AddPublisherTopic(publisherName, l3Template.TopicFilter, topicsByPublisher); + if (publisherTopicCount >= numTopicsPerPublisher) + break; - if (l2 == 0) - { - var singleWildcardTopic = l1Template - .WithParameter("sensor", "sensor" + (l3 + 1)) - .TopicFilter; - AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); - } - if ((l1 == 0) && (l3 == 0)) - { - var multiWildcardTopic = publisherTemplate - .WithParameter("level", "level" + (l2 + 1)) - .TopicFilter; - AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); - } + var l3Template = l2Template + .WithParameter("sensor", "sensor" + (l3 + 1)); + AddPublisherTopic(publisherName, l3Template.TopicFilter, topicsByPublisher); - ++publisherTopicCount; + if (l2 == 0) + { + var singleWildcardTopic = l1Template + .WithParameter("sensor", "sensor" + (l3 + 1)) + .TopicFilter; + AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); + } + if ((l1 == 0) && (l3 == 0)) + { + var multiWildcardTopic = publisherTemplate + .WithParameter("level", "level" + (l2 + 1)) + .TopicFilter; + AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); } + + ++publisherTopicCount; } } } } + } - static void AddPublisherTopic(string publisherName, string topic, Dictionary> topicsByPublisher) + static void AddPublisherTopic(string publisherName, string topic, Dictionary> topicsByPublisher) + { + List topicList; + if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) { - List topicList; - if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) - { - topicList = new List(); - topicsByPublisher.Add(publisherName, topicList); - } - topicList.Add(topic); + topicList = new List(); + topicsByPublisher.Add(publisherName, topicList); } + topicList.Add(topic); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 28d72bcf2..724d47df4 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -74,7 +74,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken) */ var timeout = new TaskCompletionSource(); - using (cancellationToken.Register(() => timeout.TrySetResult(null))) + await using (cancellationToken.Register(() => timeout.TrySetResult(null))) { var connectTask = Task.Run( async () => diff --git a/Source/MQTTnet/Adapter/MqttPacketInspector.cs b/Source/MQTTnet/Adapter/MqttPacketInspector.cs index f4ea7753b..63296840f 100644 --- a/Source/MQTTnet/Adapter/MqttPacketInspector.cs +++ b/Source/MQTTnet/Adapter/MqttPacketInspector.cs @@ -35,11 +35,7 @@ public void BeginReceivePacket() return; } - if (_receivedPacketBuffer == null) - { - _receivedPacketBuffer = new MemoryStream(); - } - + _receivedPacketBuffer ??= new MemoryStream(); _receivedPacketBuffer?.SetLength(0); } diff --git a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs index f290e5656..de49b9ba0 100644 --- a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs +++ b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Adapter; public readonly struct ReceivedMqttPacket { - public static readonly ReceivedMqttPacket Empty = new(); + public static readonly ReceivedMqttPacket Empty; public ReceivedMqttPacket(byte fixedHeader, ArraySegment body, int totalLength) { diff --git a/Source/MQTTnet/Certificates/BlobCertificateProvider.cs b/Source/MQTTnet/Certificates/BlobCertificateProvider.cs index be2024e22..7675125ca 100644 --- a/Source/MQTTnet/Certificates/BlobCertificateProvider.cs +++ b/Source/MQTTnet/Certificates/BlobCertificateProvider.cs @@ -5,28 +5,22 @@ using System; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Certificates -{ - public class BlobCertificateProvider : ICertificateProvider - { - public BlobCertificateProvider(byte[] blob) - { - Blob = blob ?? throw new ArgumentNullException(nameof(blob)); - } +namespace MQTTnet.Certificates; - public byte[] Blob { get; } +public class BlobCertificateProvider(byte[] blob) : ICertificateProvider +{ + public byte[] Blob { get; } = blob ?? throw new ArgumentNullException(nameof(blob)); - public string Password { get; set; } + public string Password { get; set; } - public X509Certificate2 GetCertificate() + public X509Certificate2 GetCertificate() + { + if (string.IsNullOrEmpty(Password)) { - if (string.IsNullOrEmpty(Password)) - { - // Use a different overload when no password is specified. Otherwise the constructor will fail. - return new X509Certificate2(Blob); - } - - return new X509Certificate2(Blob, Password); + // Use a different overload when no password is specified. Otherwise, the constructor will fail. + return new X509Certificate2(Blob); } + + return new X509Certificate2(Blob, Password); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Certificates/ICertificateProvider.cs b/Source/MQTTnet/Certificates/ICertificateProvider.cs index 58b4a9086..99ae9530a 100644 --- a/Source/MQTTnet/Certificates/ICertificateProvider.cs +++ b/Source/MQTTnet/Certificates/ICertificateProvider.cs @@ -4,10 +4,9 @@ using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Certificates +namespace MQTTnet.Certificates; + +public interface ICertificateProvider { - public interface ICertificateProvider - { - X509Certificate2 GetCertificate(); - } -} + X509Certificate2 GetCertificate(); +} \ No newline at end of file diff --git a/Source/MQTTnet/Certificates/X509CertificateProvider.cs b/Source/MQTTnet/Certificates/X509CertificateProvider.cs index e62f7f327..62463dc4e 100644 --- a/Source/MQTTnet/Certificates/X509CertificateProvider.cs +++ b/Source/MQTTnet/Certificates/X509CertificateProvider.cs @@ -5,20 +5,14 @@ using System; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Certificates -{ - public class X509CertificateProvider : ICertificateProvider - { - readonly X509Certificate2 _certificate; +namespace MQTTnet.Certificates; - public X509CertificateProvider(X509Certificate2 certificate) - { - _certificate = certificate ?? throw new ArgumentNullException(nameof(certificate)); - } +public class X509CertificateProvider(X509Certificate2 certificate) : ICertificateProvider +{ + readonly X509Certificate2 _certificate = certificate ?? throw new ArgumentNullException(nameof(certificate)); - public X509Certificate2 GetCertificate() - { - return _certificate; - } + public X509Certificate2 GetCertificate() + { + return _certificate; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Connecting/MqttClientConnectResult.cs b/Source/MQTTnet/Connecting/MqttClientConnectResult.cs index a64a4c29c..66cf0300d 100644 --- a/Source/MQTTnet/Connecting/MqttClientConnectResult.cs +++ b/Source/MQTTnet/Connecting/MqttClientConnectResult.cs @@ -14,57 +14,57 @@ public sealed class MqttClientConnectResult /// Gets the client identifier which was chosen by the server. /// MQTTv5 only. /// - public string AssignedClientIdentifier { get; internal set; } + public string AssignedClientIdentifier { get; init; } /// /// Gets the authentication data. /// MQTTv5 only. /// - public byte[] AuthenticationData { get; internal set; } + public byte[] AuthenticationData { get; init; } /// /// Gets the authentication method. /// MQTTv5 only. /// - public string AuthenticationMethod { get; internal set; } + public string AuthenticationMethod { get; init; } /// /// Gets a value indicating whether a session was already available or not. /// - public bool IsSessionPresent { get; internal set; } + public bool IsSessionPresent { get; init; } - public uint? MaximumPacketSize { get; internal set; } + public uint? MaximumPacketSize { get; init; } /// /// Gets the maximum QoS which is supported by the server. /// MQTTv5 only. /// - public MqttQualityOfServiceLevel MaximumQoS { get; internal set; } + public MqttQualityOfServiceLevel MaximumQoS { get; init; } /// /// Gets the reason string. /// MQTTv5 only. /// - public string ReasonString { get; internal set; } + public string ReasonString { get; init; } - public ushort? ReceiveMaximum { get; internal set; } + public ushort? ReceiveMaximum { get; init; } /// /// Gets the response information. /// MQTTv5 only. /// - public string ResponseInformation { get; internal set; } + public string ResponseInformation { get; init; } /// /// Gets the result code. /// - public MqttClientConnectResultCode ResultCode { get; internal set; } + public MqttClientConnectResultCode ResultCode { get; init; } /// /// Gets whether the server supports retained messages. /// MQTTv5 only. /// - public bool RetainAvailable { get; internal set; } + public bool RetainAvailable { get; init; } /// /// MQTTv5 only. @@ -72,33 +72,33 @@ public sealed class MqttClientConnectResult /// keep alive interval from the client CONNECT packet. /// A value of 0 indicates that the feature is not used. /// - public ushort ServerKeepAlive { get; internal set; } + public ushort ServerKeepAlive { get; init; } /// /// Gets an alternate server which should be used instead of the current one. /// MQTTv5 only. /// - public string ServerReference { get; internal set; } + public string ServerReference { get; init; } - public uint? SessionExpiryInterval { get; internal set; } + public uint? SessionExpiryInterval { get; init; } /// /// Gets a value indicating whether the shared subscriptions are available or not. /// MQTTv5 only. /// - public bool SharedSubscriptionAvailable { get; internal set; } + public bool SharedSubscriptionAvailable { get; init; } /// /// Gets a value indicating whether the subscription identifiers are available or not. /// MQTTv5 only. /// - public bool SubscriptionIdentifiersAvailable { get; internal set; } + public bool SubscriptionIdentifiersAvailable { get; init; } /// /// Gets the maximum value for a topic alias. 0 means not supported. /// MQTTv5 only. /// - public ushort TopicAliasMaximum { get; internal set; } + public ushort TopicAliasMaximum { get; init; } /// /// Gets the user properties. @@ -109,11 +109,11 @@ public sealed class MqttClientConnectResult /// The feature is very similar to the HTTP header concept. /// MQTTv5 only. /// - public List UserProperties { get; internal set; } + public List UserProperties { get; init; } /// /// Gets a value indicating whether wildcards can be used in subscriptions at the current server. /// MQTTv5 only. /// - public bool WildcardSubscriptionAvailable { get; internal set; } + public bool WildcardSubscriptionAvailable { get; init; } } diff --git a/Source/MQTTnet/Connecting/MqttClientConnectResultFactory.cs b/Source/MQTTnet/Connecting/MqttClientConnectResultFactory.cs index b9e442730..373b4bc55 100644 --- a/Source/MQTTnet/Connecting/MqttClientConnectResultFactory.cs +++ b/Source/MQTTnet/Connecting/MqttClientConnectResultFactory.cs @@ -10,9 +10,9 @@ namespace MQTTnet; -public sealed class MqttClientConnectResultFactory +public static class MqttClientConnectResultFactory { - public MqttClientConnectResult Create(MqttConnAckPacket connAckPacket, MqttProtocolVersion protocolVersion) + public static MqttClientConnectResult Create(MqttConnAckPacket connAckPacket, MqttProtocolVersion protocolVersion) { ArgumentNullException.ThrowIfNull(connAckPacket); @@ -26,41 +26,16 @@ public MqttClientConnectResult Create(MqttConnAckPacket connAckPacket, MqttProto static MqttClientConnectResultCode ConvertReturnCodeToResultCode(MqttConnectReturnCode connectReturnCode) { - switch (connectReturnCode) + return connectReturnCode switch { - case MqttConnectReturnCode.ConnectionAccepted: - { - return MqttClientConnectResultCode.Success; - } - - case MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion: - { - return MqttClientConnectResultCode.UnsupportedProtocolVersion; - } - - case MqttConnectReturnCode.ConnectionRefusedNotAuthorized: - { - return MqttClientConnectResultCode.NotAuthorized; - } - - case MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword: - { - return MqttClientConnectResultCode.BadUserNameOrPassword; - } - - case MqttConnectReturnCode.ConnectionRefusedIdentifierRejected: - { - return MqttClientConnectResultCode.ClientIdentifierNotValid; - } - - case MqttConnectReturnCode.ConnectionRefusedServerUnavailable: - { - return MqttClientConnectResultCode.ServerUnavailable; - } - - default: - throw new MqttProtocolViolationException("Received unexpected return code."); - } + MqttConnectReturnCode.ConnectionAccepted => MqttClientConnectResultCode.Success, + MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion => MqttClientConnectResultCode.UnsupportedProtocolVersion, + MqttConnectReturnCode.ConnectionRefusedNotAuthorized => MqttClientConnectResultCode.NotAuthorized, + MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword => MqttClientConnectResultCode.BadUserNameOrPassword, + MqttConnectReturnCode.ConnectionRefusedIdentifierRejected => MqttClientConnectResultCode.ClientIdentifierNotValid, + MqttConnectReturnCode.ConnectionRefusedServerUnavailable => MqttClientConnectResultCode.ServerUnavailable, + _ => throw new MqttProtocolViolationException("Received unexpected return code.") + }; } static MqttClientConnectResult CreateForMqtt311(MqttConnAckPacket connAckPacket) diff --git a/Source/MQTTnet/Diagnostics/Logger/IMqttNetLogger.cs b/Source/MQTTnet/Diagnostics/Logger/IMqttNetLogger.cs index a4bab322e..e1c98aa3c 100644 --- a/Source/MQTTnet/Diagnostics/Logger/IMqttNetLogger.cs +++ b/Source/MQTTnet/Diagnostics/Logger/IMqttNetLogger.cs @@ -4,12 +4,11 @@ using System; -namespace MQTTnet.Diagnostics.Logger +namespace MQTTnet.Diagnostics.Logger; + +public interface IMqttNetLogger { - public interface IMqttNetLogger - { - bool IsEnabled { get; } - - void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception); - } -} + bool IsEnabled { get; } + + void Publish(MqttNetLogLevel level, string source, string message, object[] parameters, Exception exception); +} \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetEventLogger.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetEventLogger.cs index 15c7b3aa9..8361ac062 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetEventLogger.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetEventLogger.cs @@ -4,61 +4,57 @@ using System; -namespace MQTTnet.Diagnostics.Logger +namespace MQTTnet.Diagnostics.Logger; + +/// +/// This logger fires an event when a new message was published. +/// +public sealed class MqttNetEventLogger(string logId = null) : IMqttNetLogger { - /// - /// This logger fires an event when a new message was published. - /// - public sealed class MqttNetEventLogger : IMqttNetLogger - { - public MqttNetEventLogger(string logId = null) - { - LogId = logId; - } + public event EventHandler LogMessagePublished; - public event EventHandler LogMessagePublished; + public bool IsEnabled => LogMessagePublished != null; - public bool IsEnabled => LogMessagePublished != null; + public string LogId { get; } = logId; - public string LogId { get; } + public IFormatProvider FormatProvider { get; set; } - public void Publish(MqttNetLogLevel level, string source, string message, object[] parameters, Exception exception) + public void Publish(MqttNetLogLevel level, string source, string message, object[] parameters, Exception exception) + { + var eventHandler = LogMessagePublished; + if (eventHandler == null) { - var eventHandler = LogMessagePublished; - if (eventHandler == null) - { - // No listener is attached so we can step out. - // Keep a reference to the handler because the handler - // might be null after preparing the message. - return; - } + // No listener is attached so we can step out. + // Keep a reference to the handler because the handler + // might be null after preparing the message. + return; + } - if (parameters?.Length > 0 && message?.Length > 0) + if (parameters?.Length > 0 && message?.Length > 0) + { + try { - try - { - message = string.Format(message, parameters); - } - catch (FormatException) - { - message = "MESSAGE FORMAT INVALID: " + message; - } + message = string.Format(FormatProvider, message, parameters); } - - // We only use UTC here to improve performance. Using a local date time - // would require to load the time zone settings! - var logMessage = new MqttNetLogMessage + catch (FormatException) { - LogId = LogId, - Timestamp = DateTime.UtcNow, - Source = source, - ThreadId = Environment.CurrentManagedThreadId, - Level = level, - Message = message, - Exception = exception - }; - - eventHandler.Invoke(this, new MqttNetLogMessagePublishedEventArgs(logMessage)); + message = "MESSAGE FORMAT INVALID: " + message; + } } + + // We only use UTC here to improve performance. Using a local date time + // would require to load the time zone settings! + var logMessage = new MqttNetLogMessage + { + LogId = LogId, + Timestamp = DateTime.UtcNow, + Source = source, + ThreadId = Environment.CurrentManagedThreadId, + Level = level, + Message = message, + Exception = exception + }; + + eventHandler(this, new MqttNetLogMessagePublishedEventArgs(logMessage)); } } \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetLogLevel.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetLogLevel.cs index b848b89f1..58cef6dec 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetLogLevel.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetLogLevel.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Diagnostics.Logger +namespace MQTTnet.Diagnostics.Logger; + +public enum MqttNetLogLevel { - public enum MqttNetLogLevel - { - Verbose, + Verbose, - Info, + Info, - Warning, + Warning, - Error - } -} + Error +} \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessage.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessage.cs index aea7480ea..b6d928f70 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessage.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessage.cs @@ -4,33 +4,32 @@ using System; -namespace MQTTnet.Diagnostics.Logger +namespace MQTTnet.Diagnostics.Logger; + +public sealed class MqttNetLogMessage { - public sealed class MqttNetLogMessage - { - public string LogId { get; set; } + public string LogId { get; set; } - public DateTime Timestamp { get; set; } + public DateTime Timestamp { get; set; } - public int ThreadId { get; set; } + public int ThreadId { get; set; } - public string Source { get; set; } + public string Source { get; set; } - public MqttNetLogLevel Level { get; set; } + public MqttNetLogLevel Level { get; set; } - public string Message { get; set; } + public string Message { get; set; } - public Exception Exception { get; set; } + public Exception Exception { get; set; } - public override string ToString() + public override string ToString() + { + var result = $"[{Timestamp:O}] [{LogId}] [{ThreadId}] [{Source}] [{Level}]: {Message}"; + if (Exception != null) { - var result = $"[{Timestamp:O}] [{LogId}] [{ThreadId}] [{Source}] [{Level}]: {Message}"; - if (Exception != null) - { - result += Environment.NewLine + Exception; - } - - return result; + result += Environment.NewLine + Exception; } + + return result; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessagePublishedEventArgs.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessagePublishedEventArgs.cs index c5b206d00..ca2674f27 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessagePublishedEventArgs.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetLogMessagePublishedEventArgs.cs @@ -4,15 +4,9 @@ using System; -namespace MQTTnet.Diagnostics.Logger -{ - public sealed class MqttNetLogMessagePublishedEventArgs : EventArgs - { - public MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage) - { - LogMessage = logMessage ?? throw new ArgumentNullException(nameof(logMessage)); - } +namespace MQTTnet.Diagnostics.Logger; - public MqttNetLogMessage LogMessage { get; } - } -} +public sealed class MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage) : EventArgs +{ + public MqttNetLogMessage LogMessage { get; } = logMessage ?? throw new ArgumentNullException(nameof(logMessage)); +} \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetNullLogger.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetNullLogger.cs index ad3a8f126..e1140a4b4 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetNullLogger.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetNullLogger.cs @@ -4,24 +4,18 @@ using System; -namespace MQTTnet.Diagnostics.Logger -{ - /// - /// This logger does nothing with the messages. - /// - public sealed class MqttNetNullLogger : IMqttNetLogger - { - public MqttNetNullLogger() - { - IsEnabled = false; - } +namespace MQTTnet.Diagnostics.Logger; - public static MqttNetNullLogger Instance { get; } = new MqttNetNullLogger(); +/// +/// This logger does nothing with the messages. +/// +public sealed class MqttNetNullLogger : IMqttNetLogger +{ + public static MqttNetNullLogger Instance { get; } = new(); - public bool IsEnabled { get; } + public bool IsEnabled { get; } - public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) - { - } + public void Publish(MqttNetLogLevel level, string source, string message, object[] parameters, Exception exception) + { } } \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLogger.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLogger.cs index bba77f7aa..e7ea7dcac 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLogger.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLogger.cs @@ -4,24 +4,16 @@ using System; -namespace MQTTnet.Diagnostics.Logger +namespace MQTTnet.Diagnostics.Logger; + +public sealed class MqttNetSourceLogger(IMqttNetLogger logger, string source) { - public sealed class MqttNetSourceLogger - { - readonly IMqttNetLogger _logger; - readonly string _source; + readonly IMqttNetLogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - public MqttNetSourceLogger(IMqttNetLogger logger, string source) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _source = source; - } + public bool IsEnabled => _logger.IsEnabled; - public bool IsEnabled => _logger.IsEnabled; - - public void Publish(MqttNetLogLevel logLevel, string message, object[] parameters, Exception exception) - { - _logger.Publish(logLevel, _source, message, parameters, exception); - } + public void Publish(MqttNetLogLevel logLevel, string message, object[] parameters, Exception exception) + { + _logger.Publish(logLevel, source, message, parameters, exception); } } \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs index 1c3fb1763..8b460cd4b 100644 --- a/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs +++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs @@ -4,214 +4,212 @@ using System; -namespace MQTTnet.Diagnostics.Logger -{ - /* - * The logger uses generic parameters in order to avoid boxing of parameter values like integers etc. - */ +namespace MQTTnet.Diagnostics.Logger; +/* + * The logger uses generic parameters in order to avoid boxing of parameter values like integers etc. + */ - public static class MqttNetSourceLoggerExtensions +public static class MqttNetSourceLoggerExtensions +{ + public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1) { - public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1) + if (!logger.IsEnabled) { - if (!logger.IsEnabled) - { - return; - } - - logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1 }, exception); + return; } - public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Error, message, [parameter1], exception); + } - logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1, parameter2 }, exception); + public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2) + { + if (!logger.IsEnabled) + { + return; } - public static void Error(this MqttNetSourceLogger logger, Exception exception, string message) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Error, message, [parameter1, parameter2], exception); + } - logger.Publish(MqttNetLogLevel.Error, message, null, exception); + public static void Error(this MqttNetSourceLogger logger, Exception exception, string message) + { + if (!logger.IsEnabled) + { + return; } - public static void Error(this MqttNetSourceLogger logger, string message) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Error, message, null, exception); + } - logger.Publish(MqttNetLogLevel.Error, message, null, null); + public static void Error(this MqttNetSourceLogger logger, string message) + { + if (!logger.IsEnabled) + { + return; } - public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Error, message, null, null); + } - logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1 }, null); + public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) + { + if (!logger.IsEnabled) + { + return; } - public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Info, message, [parameter1], null); + } - logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1, parameter2 }, null); + public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2) + { + if (!logger.IsEnabled) + { + return; } - public static void Info(this MqttNetSourceLogger logger, string message) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Info, message, [parameter1, parameter2], null); + } - logger.Publish(MqttNetLogLevel.Info, message, null, null); + public static void Info(this MqttNetSourceLogger logger, string message) + { + if (!logger.IsEnabled) + { + return; } - public static void Publish(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Info, message, null, null); + } - logger.Publish(logLevel, message, new object[] { parameter1 }, exception); + public static void Publish(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1) + { + if (!logger.IsEnabled) + { + return; } - public static void Publish(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(logLevel, message, [parameter1], exception); + } - logger.Publish(logLevel, message, new object[] { parameter1, parameter2 }, exception); + public static void Publish(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2) + { + if (!logger.IsEnabled) + { + return; } - public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(logLevel, message, [parameter1, parameter2], exception); + } - logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1 }, null); + public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) + { + if (!logger.IsEnabled) + { + return; } - public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Verbose, message, [parameter1], null); + } - logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2 }, null); + public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2) + { + if (!logger.IsEnabled) + { + return; } - public static void Verbose( - this MqttNetSourceLogger logger, - string message, - TParameter1 parameter1, - TParameter2 parameter2, - TParameter3 parameter3) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Verbose, message, [parameter1, parameter2], null); + } - logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2, parameter3 }, null); + public static void Verbose( + this MqttNetSourceLogger logger, + string message, + TParameter1 parameter1, + TParameter2 parameter2, + TParameter3 parameter3) + { + if (!logger.IsEnabled) + { + return; } - public static void Verbose(this MqttNetSourceLogger logger, string message) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Verbose, message, [parameter1, parameter2, parameter3], null); + } - logger.Publish(MqttNetLogLevel.Verbose, message, null, null); + public static void Verbose(this MqttNetSourceLogger logger, string message) + { + if (!logger.IsEnabled) + { + return; } - public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Verbose, message, null, null); + } - logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, exception); + public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1) + { + if (!logger.IsEnabled) + { + return; } - public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Warning, message, [parameter1], exception); + } - logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, exception); + public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2) + { + if (!logger.IsEnabled) + { + return; } - public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Warning, message, [parameter1, parameter2], exception); + } - logger.Publish(MqttNetLogLevel.Warning, message, null, exception); + public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message) + { + if (!logger.IsEnabled) + { + return; } - public static void Warning(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Warning, message, null, exception); + } - logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, null); + public static void Warning(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) + { + if (!logger.IsEnabled) + { + return; } - public static void Warning(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Warning, message, [parameter1], null); + } - logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, null); + public static void Warning(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2) + { + if (!logger.IsEnabled) + { + return; } - public static void Warning(this MqttNetSourceLogger logger, string message) - { - if (!logger.IsEnabled) - { - return; - } + logger.Publish(MqttNetLogLevel.Warning, message, [parameter1, parameter2], null); + } - logger.Publish(MqttNetLogLevel.Warning, message, null, null); + public static void Warning(this MqttNetSourceLogger logger, string message) + { + if (!logger.IsEnabled) + { + return; } - public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source) - { - ArgumentNullException.ThrowIfNull(logger); + logger.Publish(MqttNetLogLevel.Warning, message, null, null); + } - return new MqttNetSourceLogger(logger, source); - } + public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source) + { + ArgumentNullException.ThrowIfNull(logger); + + return new MqttNetSourceLogger(logger, source); } } \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs index ec7ec733e..c85784ee8 100644 --- a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs +++ b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs @@ -4,18 +4,17 @@ using System; -namespace MQTTnet.Diagnostics.PacketInspection +namespace MQTTnet.Diagnostics.PacketInspection; + +public sealed class InspectMqttPacketEventArgs : EventArgs { - public sealed class InspectMqttPacketEventArgs : EventArgs + public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer) { - public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer) - { - Direction = direction; - Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); - } + Direction = direction; + Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + } - public byte[] Buffer { get; } + public byte[] Buffer { get; } - public MqttPacketFlowDirection Direction { get; } - } + public MqttPacketFlowDirection Direction { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Diagnostics/PacketInspection/MqttPacketFlowDirection.cs b/Source/MQTTnet/Diagnostics/PacketInspection/MqttPacketFlowDirection.cs index 3d5909c11..1daee52ae 100644 --- a/Source/MQTTnet/Diagnostics/PacketInspection/MqttPacketFlowDirection.cs +++ b/Source/MQTTnet/Diagnostics/PacketInspection/MqttPacketFlowDirection.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Diagnostics.PacketInspection +namespace MQTTnet.Diagnostics.PacketInspection; + +public enum MqttPacketFlowDirection { - public enum MqttPacketFlowDirection - { - Inbound, + Inbound, - Outbound - } + Outbound } \ No newline at end of file diff --git a/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsBuilder.cs b/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsBuilder.cs index a953207b9..c8a7b88dd 100644 --- a/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsBuilder.cs +++ b/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsBuilder.cs @@ -51,11 +51,7 @@ public MqttClientDisconnectOptionsBuilder WithUserProperties(List(); - } - + _userProperties ??= []; _userProperties.Add(new MqttUserProperty(name, value)); return this; } diff --git a/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsValidator.cs b/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsValidator.cs index b8da24ea2..c151bdee4 100644 --- a/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsValidator.cs +++ b/Source/MQTTnet/Disconnecting/MqttClientDisconnectOptionsValidator.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using MQTTnet.Formatter; namespace MQTTnet; @@ -20,7 +19,7 @@ public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, Mqtt return; } - if (options.ReasonString?.Any() == true) + if (options.ReasonString?.Length > 0) { Throw(nameof(options.ReasonString)); } diff --git a/Source/MQTTnet/Exceptions/MqttClientNotConnectedException.cs b/Source/MQTTnet/Exceptions/MqttClientNotConnectedException.cs index 716ead704..e03a1b5e9 100644 --- a/Source/MQTTnet/Exceptions/MqttClientNotConnectedException.cs +++ b/Source/MQTTnet/Exceptions/MqttClientNotConnectedException.cs @@ -2,18 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +namespace MQTTnet.Exceptions; -namespace MQTTnet.Exceptions +public class MqttClientNotConnectedException : MqttCommunicationException { - public class MqttClientNotConnectedException : MqttCommunicationException + public MqttClientNotConnectedException() : base("The MQTT client is not connected.") { - public MqttClientNotConnectedException() : base("The MQTT client is not connected.") - { - } - - public MqttClientNotConnectedException(Exception innerException) : base("The MQTT client is not connected.", innerException) - { - } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs b/Source/MQTTnet/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs index 5fa745959..9880e7517 100644 --- a/Source/MQTTnet/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs +++ b/Source/MQTTnet/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs @@ -9,26 +9,17 @@ namespace MQTTnet.Exceptions; -public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException +public sealed class MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket, Exception innerException = null) : MqttCommunicationException( + $"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.", + innerException) { - public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket, Exception innerExcpetion = null) : base( - $"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.", - innerExcpetion) - { - ReasonCode = disconnectPacket.ReasonCode; - SessionExpiryInterval = disconnectPacket.SessionExpiryInterval; - ReasonString = disconnectPacket.ReasonString; - ServerReference = disconnectPacket.ServerReference; - UserProperties = disconnectPacket.UserProperties; - } + public MqttDisconnectReasonCode? ReasonCode { get; } = disconnectPacket.ReasonCode; - public MqttDisconnectReasonCode? ReasonCode { get; } + public string ReasonString { get; } = disconnectPacket.ReasonString; - public string ReasonString { get; } + public string ServerReference { get; } = disconnectPacket.ServerReference; - public string ServerReference { get; } + public uint? SessionExpiryInterval { get; } = disconnectPacket.SessionExpiryInterval; - public uint? SessionExpiryInterval { get; } - - public List UserProperties { get; } + public List UserProperties { get; } = disconnectPacket.UserProperties; } \ No newline at end of file diff --git a/Source/MQTTnet/Exceptions/MqttCommunicationException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationException.cs index a78c1f7cf..7f8a3ffdb 100644 --- a/Source/MQTTnet/Exceptions/MqttCommunicationException.cs +++ b/Source/MQTTnet/Exceptions/MqttCommunicationException.cs @@ -4,18 +4,17 @@ using System; -namespace MQTTnet.Exceptions +namespace MQTTnet.Exceptions; + +public class MqttCommunicationException : Exception { - public class MqttCommunicationException : Exception + public MqttCommunicationException(Exception innerException) + : base(innerException?.Message ?? "MQTT communication failed.", innerException) { - public MqttCommunicationException(Exception innerException) - : base(innerException?.Message ?? "MQTT communication failed.", innerException) - { - } + } - public MqttCommunicationException(string message, Exception innerException = null) - : base(message, innerException) - { - } + public MqttCommunicationException(string message, Exception innerException = null) + : base(message, innerException) + { } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Exceptions/MqttCommunicationTimedOutException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationTimedOutException.cs index 5d41c1b69..566a5871b 100644 --- a/Source/MQTTnet/Exceptions/MqttCommunicationTimedOutException.cs +++ b/Source/MQTTnet/Exceptions/MqttCommunicationTimedOutException.cs @@ -4,16 +4,15 @@ using System; -namespace MQTTnet.Exceptions +namespace MQTTnet.Exceptions; + +public sealed class MqttCommunicationTimedOutException : MqttCommunicationException { - public sealed class MqttCommunicationTimedOutException : MqttCommunicationException + public MqttCommunicationTimedOutException() : base("The operation has timed out.") { - public MqttCommunicationTimedOutException() : base("The operation has timed out.") - { - } + } - public MqttCommunicationTimedOutException(Exception innerException) : base("The operation has timed out.", innerException) - { - } + public MqttCommunicationTimedOutException(Exception innerException) : base("The operation has timed out.", innerException) + { } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Exceptions/MqttConfigurationException.cs b/Source/MQTTnet/Exceptions/MqttConfigurationException.cs index 9e2bc5803..145ee82ab 100644 --- a/Source/MQTTnet/Exceptions/MqttConfigurationException.cs +++ b/Source/MQTTnet/Exceptions/MqttConfigurationException.cs @@ -4,22 +4,21 @@ using System; -namespace MQTTnet.Exceptions +namespace MQTTnet.Exceptions; + +public class MqttConfigurationException : Exception { - public class MqttConfigurationException : Exception + protected MqttConfigurationException() { - protected MqttConfigurationException() - { - } + } - public MqttConfigurationException(Exception innerException) - : base(innerException.Message, innerException) - { - } + public MqttConfigurationException(Exception innerException) + : base(innerException.Message, innerException) + { + } - public MqttConfigurationException(string message) - : base(message) - { - } + public MqttConfigurationException(string message) + : base(message) + { } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs b/Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs index e8229a89a..f373d6430 100644 --- a/Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs +++ b/Source/MQTTnet/Exceptions/MqttProtocolViolationException.cs @@ -4,13 +4,6 @@ using System; -namespace MQTTnet.Exceptions -{ - public class MqttProtocolViolationException : Exception - { - public MqttProtocolViolationException(string message) - : base(message) - { - } - } -} +namespace MQTTnet.Exceptions; + +public class MqttProtocolViolationException(string message) : Exception(message); \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs index a6e8c5550..7754d8b27 100644 --- a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs +++ b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs @@ -5,12 +5,11 @@ using MQTTnet.Adapter; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public interface IMqttPacketFormatter { - public interface IMqttPacketFormatter - { - MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket); + MqttPacket Decode(ReceivedMqttPacket receivedPacket); - MqttPacketBuffer Encode(MqttPacket mqttPacket); - } + MqttPacketBuffer Encode(MqttPacket packet); } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs index 60cbcbb12..8c81d990c 100644 --- a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs +++ b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs @@ -5,30 +5,29 @@ using System; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttApplicationMessageFactory { - public static class MqttApplicationMessageFactory + public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) { - public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) - { - ArgumentNullException.ThrowIfNull(publishPacket); + ArgumentNullException.ThrowIfNull(publishPacket); - return new MqttApplicationMessage - { - Topic = publishPacket.Topic, - Payload = publishPacket.Payload, - QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, - Retain = publishPacket.Retain, - Dup = publishPacket.Dup, - ResponseTopic = publishPacket.ResponseTopic, - ContentType = publishPacket.ContentType, - CorrelationData = publishPacket.CorrelationData, - MessageExpiryInterval = publishPacket.MessageExpiryInterval, - SubscriptionIdentifiers = publishPacket.SubscriptionIdentifiers, - TopicAlias = publishPacket.TopicAlias, - PayloadFormatIndicator = publishPacket.PayloadFormatIndicator, - UserProperties = publishPacket.UserProperties - }; - } + return new MqttApplicationMessage + { + Topic = publishPacket.Topic, + Payload = publishPacket.Payload, + QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, + Retain = publishPacket.Retain, + Dup = publishPacket.Dup, + ResponseTopic = publishPacket.ResponseTopic, + ContentType = publishPacket.ContentType, + CorrelationData = publishPacket.CorrelationData, + MessageExpiryInterval = publishPacket.MessageExpiryInterval, + SubscriptionIdentifiers = publishPacket.SubscriptionIdentifiers, + TopicAlias = publishPacket.TopicAlias, + PayloadFormatIndicator = publishPacket.PayloadFormatIndicator, + UserProperties = publishPacket.UserProperties + }; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttBufferReader.cs b/Source/MQTTnet/Formatter/MqttBufferReader.cs index c8102be7e..c6ca138e1 100644 --- a/Source/MQTTnet/Formatter/MqttBufferReader.cs +++ b/Source/MQTTnet/Formatter/MqttBufferReader.cs @@ -10,146 +10,145 @@ using System.Buffers.Binary; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public sealed class MqttBufferReader { - public sealed class MqttBufferReader - { - byte[] _buffer = EmptyBuffer.Array; - int _maxPosition; - int _offset; - int _position; + byte[] _buffer = EmptyBuffer.Array; + int _maxPosition; + int _offset; + int _position; - public int BytesLeft => _maxPosition - _position; + public int BytesLeft => _maxPosition - _position; - public bool EndOfStream => BytesLeft == 0; + public bool EndOfStream => BytesLeft == 0; - public int Position => _position - _offset; + public int Position => _position - _offset; + + public byte[] ReadBinaryData() + { + var length = ReadTwoByteInteger(); - public byte[] ReadBinaryData() + if (length == 0) { - var length = ReadTwoByteInteger(); + return EmptyBuffer.Array; + } - if (length == 0) - { - return EmptyBuffer.Array; - } + ValidateReceiveBuffer(length); - ValidateReceiveBuffer(length); + var result = GC.AllocateUninitializedArray(length); + MqttMemoryHelper.Copy(_buffer, _position, result, 0, length); + _position += length; - var result = GC.AllocateUninitializedArray(length); - MqttMemoryHelper.Copy(_buffer, _position, result, 0, length); - _position += length; + return result; + } - return result; - } + public byte ReadByte() + { + ValidateReceiveBuffer(1); + return _buffer[_position++]; + } - public byte ReadByte() - { - ValidateReceiveBuffer(1); - return _buffer[_position++]; - } + public uint ReadFourByteInteger() + { + ValidateReceiveBuffer(4); - public uint ReadFourByteInteger() - { - ValidateReceiveBuffer(4); + var value = BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(_position)); - var value = BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(_position)); + _position += 4; + return value; + } - _position += 4; - return value; + public byte[] ReadRemainingData() + { + var bufferLength = BytesLeft; + if (bufferLength == 0) + { + return EmptyBuffer.Array; } - public byte[] ReadRemainingData() - { - var bufferLength = BytesLeft; - if (bufferLength == 0) - { - return EmptyBuffer.Array; - } + var buffer = GC.AllocateUninitializedArray(bufferLength); + MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength); + _position += bufferLength; - var buffer = GC.AllocateUninitializedArray(bufferLength); - MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength); - _position += bufferLength; + return buffer; + } - return buffer; - } + public string ReadString() + { + var length = ReadTwoByteInteger(); - public string ReadString() + if (length == 0) { - var length = ReadTwoByteInteger(); + return string.Empty; + } - if (length == 0) - { - return string.Empty; - } + ValidateReceiveBuffer(length); - ValidateReceiveBuffer(length); + // AsSpan() version is slightly faster. Not much but at least a little bit. + var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length)); - // AsSpan() version is slightly faster. Not much but at least a little bit. - var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length)); + _position += length; + return result; + } - _position += length; - return result; - } + public ushort ReadTwoByteInteger() + { + ValidateReceiveBuffer(2); - public ushort ReadTwoByteInteger() - { - ValidateReceiveBuffer(2); + var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(_position)); - var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(_position)); + _position += 2; + return value; + } - _position += 2; - return value; - } + public uint ReadVariableByteInteger() + { + var multiplier = 1; + var value = 0U; + byte encodedByte; - public uint ReadVariableByteInteger() + do { - var multiplier = 1; - var value = 0U; - byte encodedByte; + encodedByte = ReadByte(); + value += (uint)((encodedByte & 127) * multiplier); - do + if (multiplier > 2097152) { - encodedByte = ReadByte(); - value += (uint)((encodedByte & 127) * multiplier); - - if (multiplier > 2097152) - { - throw new MqttProtocolViolationException("Variable length integer is invalid."); - } + throw new MqttProtocolViolationException("Variable length integer is invalid."); + } - multiplier *= 128; - } while ((encodedByte & 128) != 0); + multiplier *= 128; + } while ((encodedByte & 128) != 0); - return value; - } + return value; + } - public void Seek(int position) - { - _position = _offset + position; - } + public void Seek(int position) + { + _position = _offset + position; + } - public void SetBuffer(ArraySegment buffer) - { - SetBuffer(buffer.Array, buffer.Offset, buffer.Count); - } + public void SetBuffer(ArraySegment buffer) + { + SetBuffer(buffer.Array, buffer.Offset, buffer.Count); + } - public void SetBuffer(byte[] buffer, int offset, int length) - { - _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); - _offset = offset; - _position = offset; - _maxPosition = offset + length; - } + public void SetBuffer(byte[] buffer, int offset, int length) + { + _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + _offset = offset; + _position = offset; + _maxPosition = offset + length; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void ValidateReceiveBuffer(int length) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ValidateReceiveBuffer(int length) + { + var newPosition = _position + length; + if (_maxPosition < newPosition) { - var newPosition = _position + length; - if (_maxPosition < newPosition) - { - throw new MqttProtocolViolationException($"Expected at least {newPosition} bytes but there are only {_maxPosition} bytes"); - } + throw new MqttProtocolViolationException($"Expected at least {newPosition} bytes but there are only {_maxPosition} bytes"); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttBufferWriter.cs b/Source/MQTTnet/Formatter/MqttBufferWriter.cs index c89ccad76..abaca7da8 100644 --- a/Source/MQTTnet/Formatter/MqttBufferWriter.cs +++ b/Source/MQTTnet/Formatter/MqttBufferWriter.cs @@ -9,287 +9,286 @@ using MQTTnet.Internal; using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +/// +/// This is a custom implementation of a memory stream which provides only MQTTnet relevant features. +/// The goal is to avoid lots of argument checks like in the original stream. The growth rule is the +/// same as for the original MemoryStream in .net. Also this implementation allows accessing the internal +/// buffer for all platforms and .net framework versions (which is not available at the regular MemoryStream). +/// +public sealed class MqttBufferWriter { - /// - /// This is a custom implementation of a memory stream which provides only MQTTnet relevant features. - /// The goal is to avoid lots of argument checks like in the original stream. The growth rule is the - /// same as for the original MemoryStream in .net. Also this implementation allows accessing the internal - /// buffer for all platforms and .net framework versions (which is not available at the regular MemoryStream). - /// - public sealed class MqttBufferWriter - { - const uint VariableByteIntegerMaxValue = 268435455; - const int EncodedStringMaxLength = 65535; + const uint VariableByteIntegerMaxValue = 268435455; + const int EncodedStringMaxLength = 65535; - readonly int _maxBufferSize; + readonly int _maxBufferSize; - byte[] _buffer; - int _position; + byte[] _buffer; + int _position; - public MqttBufferWriter(int bufferSize, int maxBufferSize) - { - _buffer = new byte[bufferSize]; - _maxBufferSize = maxBufferSize; - } + public MqttBufferWriter(int bufferSize, int maxBufferSize) + { + _buffer = new byte[bufferSize]; + _maxBufferSize = maxBufferSize; + } + + public int Length { get; private set; } + + public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0) + { + var fixedHeader = (int)packetType << 4; + fixedHeader |= flags; + return (byte)fixedHeader; + } - public int Length { get; private set; } + public void Cleanup() + { + // This method frees the used memory by shrinking the buffer. This is required because the buffer + // is used across several messages. In general this is not a big issue because subsequent Ping packages + // have the same size but a very big publish package with 100 MB of payload will increase the buffer + // a lot and the size will never reduced. So this method tries to find a size which can be held in + // memory for a long time without causing troubles. - public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0) + if (_buffer.Length <= _maxBufferSize) { - var fixedHeader = (int)packetType << 4; - fixedHeader |= flags; - return (byte)fixedHeader; + return; } - public void Cleanup() - { - // This method frees the used memory by shrinking the buffer. This is required because the buffer - // is used across several messages. In general this is not a big issue because subsequent Ping packages - // have the same size but a very big publish package with 100 MB of payload will increase the buffer - // a lot and the size will never reduced. So this method tries to find a size which can be held in - // memory for a long time without causing troubles. + // Create a new and empty buffer. Do not use Array.Resize because it will copy all data from + // the old array to the new one which is not required in this case. + _buffer = new byte[_maxBufferSize]; + } - if (_buffer.Length <= _maxBufferSize) - { - return; - } + public byte[] GetBuffer() + { + return _buffer; + } + + public static int GetVariableByteIntegerSize(uint value) + { + // From RFC: Table 2.4 Size of Remaining Length field - // Create a new and empty buffer. Do not use Array.Resize because it will copy all data from - // the old array to the new one which is not required in this case. - _buffer = new byte[_maxBufferSize]; + // 0 (0x00) to 127 (0x7F) + if (value <= 127) + { + return 1; } - public byte[] GetBuffer() + // 128 (0x80, 0x01) to 16 383 (0xFF, 0x7F) + if (value <= 16383) { - return _buffer; + return 2; } - public static int GetVariableByteIntegerSize(uint value) + // 16 384 (0x80, 0x80, 0x01) to 2 097 151 (0xFF, 0xFF, 0x7F) + if (value <= 2097151) { - // From RFC: Table 2.4 Size of Remaining Length field + return 3; + } - // 0 (0x00) to 127 (0x7F) - if (value <= 127) - { - return 1; - } + // 2 097 152 (0x80, 0x80, 0x80, 0x01) to 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F) + return 4; + } - // 128 (0x80, 0x01) to 16 383 (0xFF, 0x7F) - if (value <= 16383) - { - return 2; - } + public void Reset(int length) + { + _position = 0; + Length = length; + } - // 16 384 (0x80, 0x80, 0x01) to 2 097 151 (0xFF, 0xFF, 0x7F) - if (value <= 2097151) - { - return 3; - } + public void Seek(int position) + { + EnsureCapacity(position); + _position = position; + } - // 2 097 152 (0x80, 0x80, 0x80, 0x01) to 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F) - return 4; - } + public void Write(MqttBufferWriter propertyWriter) + { + ArgumentNullException.ThrowIfNull(propertyWriter); - public void Reset(int length) - { - _position = 0; - Length = length; - } + WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length); + } - public void Seek(int position) + public void WriteBinary(byte[] value) + { + if (value == null || value.Length == 0) { - EnsureCapacity(position); - _position = position; - } + EnsureAdditionalCapacity(2); - public void Write(MqttBufferWriter propertyWriter) - { - ArgumentNullException.ThrowIfNull(propertyWriter); + _buffer[_position] = 0; + _buffer[_position + 1] = 0; - WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length); + IncreasePosition(2); } - - public void WriteBinary(byte[] value) + else { - if (value == null || value.Length == 0) - { - EnsureAdditionalCapacity(2); + var valueLength = value.Length; - _buffer[_position] = 0; - _buffer[_position + 1] = 0; + EnsureAdditionalCapacity(valueLength + 2); - IncreasePosition(2); - } - else - { - var valueLength = value.Length; + _buffer[_position] = (byte)(valueLength >> 8); + _buffer[_position + 1] = (byte)valueLength; - EnsureAdditionalCapacity(valueLength + 2); + MqttMemoryHelper.Copy(value, 0, _buffer, _position + 2, valueLength); + IncreasePosition(valueLength + 2); + } + } - _buffer[_position] = (byte)(valueLength >> 8); - _buffer[_position + 1] = (byte)valueLength; + public void WriteBinary(byte[] buffer, int offset, int count) + { + ArgumentNullException.ThrowIfNull(buffer); - MqttMemoryHelper.Copy(value, 0, _buffer, _position + 2, valueLength); - IncreasePosition(valueLength + 2); - } + if (count == 0) + { + return; } - public void WriteBinary(byte[] buffer, int offset, int count) - { - ArgumentNullException.ThrowIfNull(buffer); + EnsureAdditionalCapacity(count); - if (count == 0) - { - return; - } + MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count); + IncreasePosition(count); + } - EnsureAdditionalCapacity(count); + public void WriteByte(byte @byte) + { + EnsureAdditionalCapacity(1); - MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count); - IncreasePosition(count); - } + _buffer[_position] = @byte; + IncreasePosition(1); + } - public void WriteByte(byte @byte) + public void WriteString(string value) + { + if (string.IsNullOrEmpty(value)) { - EnsureAdditionalCapacity(1); + EnsureAdditionalCapacity(2); - _buffer[_position] = @byte; - IncreasePosition(1); - } + _buffer[_position] = 0; + _buffer[_position + 1] = 0; - public void WriteString(string value) + IncreasePosition(2); + } + else { - if (string.IsNullOrEmpty(value)) - { - EnsureAdditionalCapacity(2); + // Do not use Encoding.UTF8.GetByteCount(value); + // UTF8 chars can have a max length of 4 and the used buffer increase *2 every time. + // So the buffer should always have much more capacity left so that a correct value + // here is only waste of CPU cycles. + var byteCount = value.Length * 4; - _buffer[_position] = 0; - _buffer[_position + 1] = 0; + EnsureAdditionalCapacity(byteCount + 2); - IncreasePosition(2); - } - else + var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2); + + // From RFC: 1.5.4 UTF-8 Encoded String + // Unless stated otherwise all UTF-8 encoded strings can have any length in the range 0 to 65,535 bytes. + if (writtenBytes > EncodedStringMaxLength) { - // Do not use Encoding.UTF8.GetByteCount(value); - // UTF8 chars can have a max length of 4 and the used buffer increase *2 every time. - // So the buffer should always have much more capacity left so that a correct value - // here is only waste of CPU cycles. - var byteCount = value.Length * 4; + throw new MqttProtocolViolationException($"The maximum string length is 65535. The current string has a length of {writtenBytes}."); + } - EnsureAdditionalCapacity(byteCount + 2); + _buffer[_position] = (byte)(writtenBytes >> 8); + _buffer[_position + 1] = (byte)writtenBytes; - var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2); + IncreasePosition(writtenBytes + 2); + } + } - // From RFC: 1.5.4 UTF-8 Encoded String - // Unless stated otherwise all UTF-8 encoded strings can have any length in the range 0 to 65,535 bytes. - if (writtenBytes > EncodedStringMaxLength) - { - throw new MqttProtocolViolationException($"The maximum string length is 65535. The current string has a length of {writtenBytes}."); - } + public void WriteTwoByteInteger(ushort value) + { + EnsureAdditionalCapacity(2); - _buffer[_position] = (byte)(writtenBytes >> 8); - _buffer[_position + 1] = (byte)writtenBytes; + _buffer[_position] = (byte)(value >> 8); + IncreasePosition(1); + _buffer[_position] = (byte)value; + IncreasePosition(1); + } - IncreasePosition(writtenBytes + 2); - } + public void WriteVariableByteInteger(uint value) + { + if (value == 0) + { + _buffer[_position] = 0; + IncreasePosition(1); + + return; } - public void WriteTwoByteInteger(ushort value) + if (value <= 127) { - EnsureAdditionalCapacity(2); - - _buffer[_position] = (byte)(value >> 8); - IncreasePosition(1); _buffer[_position] = (byte)value; IncreasePosition(1); + + return; } - public void WriteVariableByteInteger(uint value) + if (value > VariableByteIntegerMaxValue) { - if (value == 0) - { - _buffer[_position] = 0; - IncreasePosition(1); + throw new MqttProtocolViolationException($"The specified value ({value}) is too large for a variable byte integer."); + } - return; + var size = 0; + var x = value; + do + { + var encodedByte = x % 128; + x /= 128; + if (x > 0) + { + encodedByte |= 128; } - if (value <= 127) - { - _buffer[_position] = (byte)value; - IncreasePosition(1); + _buffer[_position + size] = (byte)encodedByte; + size++; + } while (x > 0); - return; - } + IncreasePosition(size); + } - if (value > VariableByteIntegerMaxValue) - { - throw new MqttProtocolViolationException($"The specified value ({value}) is too large for a variable byte integer."); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void EnsureAdditionalCapacity(int additionalCapacity) + { + var bufferLength = _buffer.Length; - var size = 0; - var x = value; - do - { - var encodedByte = x % 128; - x /= 128; - if (x > 0) - { - encodedByte |= 128; - } - - _buffer[_position + size] = (byte)encodedByte; - size++; - } while (x > 0); - - IncreasePosition(size); + var freeSpace = bufferLength - _position; + if (freeSpace >= additionalCapacity) + { + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void EnsureAdditionalCapacity(int additionalCapacity) - { - var bufferLength = _buffer.Length; + EnsureCapacity(bufferLength + additionalCapacity - freeSpace); + } - var freeSpace = bufferLength - _position; - if (freeSpace >= additionalCapacity) - { - return; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void EnsureCapacity(int capacity) + { + var newBufferLength = _buffer.Length; - EnsureCapacity(bufferLength + additionalCapacity - freeSpace); + if (newBufferLength >= capacity) + { + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void EnsureCapacity(int capacity) + while (newBufferLength < capacity) { - var newBufferLength = _buffer.Length; - - if (newBufferLength >= capacity) - { - return; - } + newBufferLength *= 2; + } - while (newBufferLength < capacity) - { - newBufferLength *= 2; - } + // Array.Resize will create a new array and copy the existing data to the new one. + Array.Resize(ref _buffer, newBufferLength); + } - // Array.Resize will create a new array and copy the existing data to the new one. - Array.Resize(ref _buffer, newBufferLength); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void IncreasePosition(int length) + { + _position += length; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void IncreasePosition(int length) + if (_position > Length) { - _position += length; - - if (_position > Length) - { - // Also extend the position because we reached the end of the - // pre allocated buffer. - Length = _position; - } + // Also extend the position because we reached the end of the + // pre allocated buffer. + Length = _position; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs b/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs index 89dda0598..a93e070ff 100644 --- a/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs @@ -33,22 +33,24 @@ public static MqttConnectPacket Create(MqttClientOptions clientOptions) TryPrivate = clientOptions.TryPrivate }; - if (!string.IsNullOrEmpty(clientOptions.WillTopic)) + if (string.IsNullOrEmpty(clientOptions.WillTopic)) { - connectPacket.WillFlag = true; - connectPacket.WillTopic = clientOptions.WillTopic; - connectPacket.WillQoS = clientOptions.WillQualityOfServiceLevel; - connectPacket.WillMessage = clientOptions.WillPayload; - connectPacket.WillRetain = clientOptions.WillRetain; - connectPacket.WillDelayInterval = clientOptions.WillDelayInterval; - connectPacket.WillContentType = clientOptions.WillContentType; - connectPacket.WillCorrelationData = clientOptions.WillCorrelationData; - connectPacket.WillResponseTopic = clientOptions.WillResponseTopic; - connectPacket.WillMessageExpiryInterval = clientOptions.WillMessageExpiryInterval; - connectPacket.WillPayloadFormatIndicator = clientOptions.WillPayloadFormatIndicator; - connectPacket.WillUserProperties = clientOptions.WillUserProperties; + return connectPacket; } + connectPacket.WillFlag = true; + connectPacket.WillTopic = clientOptions.WillTopic; + connectPacket.WillQoS = clientOptions.WillQualityOfServiceLevel; + connectPacket.WillMessage = clientOptions.WillPayload; + connectPacket.WillRetain = clientOptions.WillRetain; + connectPacket.WillDelayInterval = clientOptions.WillDelayInterval; + connectPacket.WillContentType = clientOptions.WillContentType; + connectPacket.WillCorrelationData = clientOptions.WillCorrelationData; + connectPacket.WillResponseTopic = clientOptions.WillResponseTopic; + connectPacket.WillMessageExpiryInterval = clientOptions.WillMessageExpiryInterval; + connectPacket.WillPayloadFormatIndicator = clientOptions.WillPayloadFormatIndicator; + connectPacket.WillUserProperties = clientOptions.WillUserProperties; + return connectPacket; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttConnectReasonCodeConverter.cs b/Source/MQTTnet/Formatter/MqttConnectReasonCodeConverter.cs index cad1799b1..a4bf70a65 100644 --- a/Source/MQTTnet/Formatter/MqttConnectReasonCodeConverter.cs +++ b/Source/MQTTnet/Formatter/MqttConnectReasonCodeConverter.cs @@ -4,52 +4,51 @@ using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttConnectReasonCodeConverter { - public static class MqttConnectReasonCodeConverter + public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode) { - public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode) + switch (reasonCode) { - switch (reasonCode) + case MqttConnectReasonCode.Success: { - case MqttConnectReasonCode.Success: - { - return MqttConnectReturnCode.ConnectionAccepted; - } - - case MqttConnectReasonCode.Banned: - case MqttConnectReasonCode.NotAuthorized: - { - return MqttConnectReturnCode.ConnectionRefusedNotAuthorized; - } - - case MqttConnectReasonCode.BadAuthenticationMethod: - case MqttConnectReasonCode.BadUserNameOrPassword: - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - - case MqttConnectReasonCode.ClientIdentifierNotValid: - { - return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; - } - - case MqttConnectReasonCode.UnsupportedProtocolVersion: - { - return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion; - } - - case MqttConnectReasonCode.UseAnotherServer: - case MqttConnectReasonCode.ServerUnavailable: - case MqttConnectReasonCode.ServerBusy: - case MqttConnectReasonCode.ServerMoved: - { - return MqttConnectReturnCode.ConnectionRefusedServerUnavailable; - } - - default: - return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion; + return MqttConnectReturnCode.ConnectionAccepted; } + + case MqttConnectReasonCode.Banned: + case MqttConnectReasonCode.NotAuthorized: + { + return MqttConnectReturnCode.ConnectionRefusedNotAuthorized; + } + + case MqttConnectReasonCode.BadAuthenticationMethod: + case MqttConnectReasonCode.BadUserNameOrPassword: + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + + case MqttConnectReasonCode.ClientIdentifierNotValid: + { + return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; + } + + case MqttConnectReasonCode.UnsupportedProtocolVersion: + { + return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion; + } + + case MqttConnectReasonCode.UseAnotherServer: + case MqttConnectReasonCode.ServerUnavailable: + case MqttConnectReasonCode.ServerBusy: + case MqttConnectReasonCode.ServerMoved: + { + return MqttConnectReturnCode.ConnectionRefusedServerUnavailable; + } + + default: + return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttFixedHeader.cs b/Source/MQTTnet/Formatter/MqttFixedHeader.cs index cb446d622..3460a47d2 100644 --- a/Source/MQTTnet/Formatter/MqttFixedHeader.cs +++ b/Source/MQTTnet/Formatter/MqttFixedHeader.cs @@ -2,21 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Formatter -{ - public struct MqttFixedHeader - { - public MqttFixedHeader(byte flags, int remainingLength, int totalLength) - { - Flags = flags; - RemainingLength = remainingLength; - TotalLength = totalLength; - } +namespace MQTTnet.Formatter; - public byte Flags { get; } +public struct MqttFixedHeader(byte flags, int remainingLength, int totalLength) +{ + public byte Flags { get; } = flags; - public int RemainingLength { get; } + public int RemainingLength { get; } = remainingLength; - public int TotalLength { get; } - } + public int TotalLength { get; } = totalLength; } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index b87939e73..91376e350 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -6,53 +6,53 @@ using System; using System.Buffers; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public readonly struct MqttPacketBuffer { - public readonly struct MqttPacketBuffer + public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payload) { - public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payload) - { - Packet = packet; - Payload = payload; + Packet = packet; + Payload = payload; - Length = Packet.Count + (int)Payload.Length; - } + Length = Packet.Count + (int)Payload.Length; + } - public MqttPacketBuffer(ArraySegment packet) - { - Packet = packet; - Payload = EmptyBuffer.ReadOnlySequence; + public MqttPacketBuffer(ArraySegment packet) + { + Packet = packet; + Payload = EmptyBuffer.ReadOnlySequence; - Length = Packet.Count; - } + Length = Packet.Count; + } - public int Length { get; } + public int Length { get; } - public ArraySegment Packet { get; } + public ArraySegment Packet { get; } - public ReadOnlySequence Payload { get; } + public ReadOnlySequence Payload { get; } - public byte[] ToArray() + public byte[] ToArray() + { + if (Payload.Length == 0) { - if (Payload.Length == 0) - { - return Packet.ToArray(); - } + return Packet.ToArray(); + } - var buffer = GC.AllocateUninitializedArray(Length); - MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); - MqttMemoryHelper.Copy(Payload, 0, buffer, Packet.Count, (int)Payload.Length); + var buffer = GC.AllocateUninitializedArray(Length); + MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); + MqttMemoryHelper.Copy(Payload, 0, buffer, Packet.Count, (int)Payload.Length); - return buffer; - } + return buffer; + } - public ArraySegment Join() + public ArraySegment Join() + { + if (Payload.Length == 0) { - if (Payload.Length == 0) - { - return Packet; - } - return new ArraySegment(this.ToArray()); + return Packet; } + + return new ArraySegment(ToArray()); } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs index 7022740a4..4f9743696 100644 --- a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs @@ -10,141 +10,130 @@ using MQTTnet.Formatter.V5; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public sealed class MqttPacketFormatterAdapter { - public sealed class MqttPacketFormatterAdapter + readonly MqttBufferReader _bufferReader = new(); + readonly MqttBufferWriter _bufferWriter; + + IMqttPacketFormatter _formatter; + + public MqttPacketFormatterAdapter(MqttBufferWriter mqttBufferWriter) { - readonly MqttBufferReader _bufferReader = new MqttBufferReader(); - readonly MqttBufferWriter _bufferWriter; + _bufferWriter = mqttBufferWriter ?? throw new ArgumentNullException(nameof(mqttBufferWriter)); + } - IMqttPacketFormatter _formatter; + public MqttPacketFormatterAdapter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter) + : this(bufferWriter) + { + UseProtocolVersion(protocolVersion); + } - public MqttPacketFormatterAdapter(MqttBufferWriter mqttBufferWriter) - { - _bufferWriter = mqttBufferWriter ?? throw new ArgumentNullException(nameof(mqttBufferWriter)); - } + public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown; - public MqttPacketFormatterAdapter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter) - : this(bufferWriter) - { - UseProtocolVersion(protocolVersion); - } + public void Cleanup() + { + _bufferWriter.Cleanup(); + } - public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown; + public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) + { + ThrowIfFormatterNotSet(); + return _formatter.Decode(receivedMqttPacket); + } - public void Cleanup() - { - _bufferWriter.Cleanup(); - } + public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket) + { + var protocolVersion = ParseProtocolVersion(receivedMqttPacket); + UseProtocolVersion(protocolVersion); + } - public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) - { - ThrowIfFormatterNotSet(); - return _formatter.Decode(receivedMqttPacket); - } + public MqttPacketBuffer Encode(MqttPacket packet) + { + ThrowIfFormatterNotSet(); + return _formatter.Encode(packet); + } - public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket) + public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter) + { + if (protocolVersion == MqttProtocolVersion.Unknown) { - var protocolVersion = ParseProtocolVersion(receivedMqttPacket); - UseProtocolVersion(protocolVersion); + throw new InvalidOperationException("MQTT protocol version is invalid."); } - public MqttPacketBuffer Encode(MqttPacket packet) + return protocolVersion switch { - ThrowIfFormatterNotSet(); - return _formatter.Encode(packet); - } + MqttProtocolVersion.V500 => new MqttV5PacketFormatter(bufferWriter), + MqttProtocolVersion.V310 or MqttProtocolVersion.V311 => new MqttV3PacketFormatter(bufferWriter, protocolVersion), + _ => throw new NotSupportedException() + }; + } - public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter) + MqttProtocolVersion ParseProtocolVersion(ReceivedMqttPacket receivedMqttPacket) + { + if (receivedMqttPacket.Body.Count < 7) { - if (protocolVersion == MqttProtocolVersion.Unknown) - { - throw new InvalidOperationException("MQTT protocol version is invalid."); - } - - switch (protocolVersion) - { - case MqttProtocolVersion.V500: - { - return new MqttV5PacketFormatter(bufferWriter); - } - case MqttProtocolVersion.V310: - case MqttProtocolVersion.V311: - { - return new MqttV3PacketFormatter(bufferWriter, protocolVersion); - } - default: - { - throw new NotSupportedException(); - } - } + // 2 byte protocol name length + // at least 4 byte protocol name + // 1 byte protocol level + throw new MqttProtocolViolationException("CONNECT packet must have at least 7 bytes."); } - MqttProtocolVersion ParseProtocolVersion(ReceivedMqttPacket receivedMqttPacket) - { - if (receivedMqttPacket.Body.Count < 7) - { - // 2 byte protocol name length - // at least 4 byte protocol name - // 1 byte protocol level - throw new MqttProtocolViolationException("CONNECT packet must have at least 7 bytes."); - } - - _bufferReader.SetBuffer(receivedMqttPacket.Body.Array, receivedMqttPacket.Body.Offset, receivedMqttPacket.Body.Count); + _bufferReader.SetBuffer(receivedMqttPacket.Body.Array, receivedMqttPacket.Body.Offset, receivedMqttPacket.Body.Count); - var protocolName = _bufferReader.ReadString(); - var protocolLevel = _bufferReader.ReadByte(); + var protocolName = _bufferReader.ReadString(); + var protocolLevel = _bufferReader.ReadByte(); - // Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge). - // This flag is accepted but not yet used. - protocolLevel &= 0x7F; + // Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge). + // This flag is accepted but not yet used. + protocolLevel &= 0x7F; - if (protocolName == "MQTT") + if (protocolName == "MQTT") + { + if (protocolLevel == 5) { - if (protocolLevel == 5) - { - return MqttProtocolVersion.V500; - } - - if (protocolLevel == 4) - { - return MqttProtocolVersion.V311; - } - - throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported."); + return MqttProtocolVersion.V500; } - if (protocolName == "MQIsdp") + if (protocolLevel == 4) { - if (protocolLevel == 3) - { - return MqttProtocolVersion.V310; - } - - throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported."); + return MqttProtocolVersion.V311; } - throw new MqttProtocolViolationException($"Protocol '{protocolName}' not supported."); + throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported."); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void ThrowIfFormatterNotSet() + if (protocolName == "MQIsdp") { - if (_formatter == null) + if (protocolLevel == 3) { - throw new InvalidOperationException("Protocol version not set or detected."); + return MqttProtocolVersion.V310; } + + throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported."); } - void UseProtocolVersion(MqttProtocolVersion protocolVersion) + throw new MqttProtocolViolationException($"Protocol '{protocolName}' not supported."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ThrowIfFormatterNotSet() + { + if (_formatter == null) { - if (protocolVersion == MqttProtocolVersion.Unknown) - { - throw new InvalidOperationException("MQTT protocol version is invalid."); - } + throw new InvalidOperationException("Protocol version not set or detected."); + } + } - ProtocolVersion = protocolVersion; - _formatter = GetMqttPacketFormatter(protocolVersion, _bufferWriter); + void UseProtocolVersion(MqttProtocolVersion protocolVersion) + { + if (protocolVersion == MqttProtocolVersion.Unknown) + { + throw new InvalidOperationException("MQTT protocol version is invalid."); } + + ProtocolVersion = protocolVersion; + _formatter = GetMqttPacketFormatter(protocolVersion, _bufferWriter); } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttProtocolVersion.cs b/Source/MQTTnet/Formatter/MqttProtocolVersion.cs index 436951def..ad852489d 100644 --- a/Source/MQTTnet/Formatter/MqttProtocolVersion.cs +++ b/Source/MQTTnet/Formatter/MqttProtocolVersion.cs @@ -2,14 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public enum MqttProtocolVersion { - public enum MqttProtocolVersion - { - Unknown = 0, + Unknown = 0, - V310 = 3, - V311 = 4, - V500 = 5 - } + V310 = 3, + V311 = 4, + V500 = 5 } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index daf0237b7..685e4d150 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -3,37 +3,35 @@ // See the LICENSE file in the project root for more information. using System; -using MQTTnet.Exceptions; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttPublishPacketFactory { - public static class MqttPublishPacketFactory + public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage) { - public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage) - { - ArgumentNullException.ThrowIfNull(applicationMessage); + ArgumentNullException.ThrowIfNull(applicationMessage); - // Copy all values to their matching counterparts. - // The not supported values in MQTT 3.1.1 are not serialized (excluded) later. - var packet = new MqttPublishPacket - { - Topic = applicationMessage.Topic, - Payload = applicationMessage.Payload, - QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, - Retain = applicationMessage.Retain, - Dup = applicationMessage.Dup, - ContentType = applicationMessage.ContentType, - CorrelationData = applicationMessage.CorrelationData, - MessageExpiryInterval = applicationMessage.MessageExpiryInterval, - PayloadFormatIndicator = applicationMessage.PayloadFormatIndicator, - ResponseTopic = applicationMessage.ResponseTopic, - TopicAlias = applicationMessage.TopicAlias, - SubscriptionIdentifiers = applicationMessage.SubscriptionIdentifiers, - UserProperties = applicationMessage.UserProperties - }; + // Copy all values to their matching counterparts. + // The not supported values in MQTT 3.1.1 are not serialized (excluded) later. + var packet = new MqttPublishPacket + { + Topic = applicationMessage.Topic, + Payload = applicationMessage.Payload, + QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, + Retain = applicationMessage.Retain, + Dup = applicationMessage.Dup, + ContentType = applicationMessage.ContentType, + CorrelationData = applicationMessage.CorrelationData, + MessageExpiryInterval = applicationMessage.MessageExpiryInterval, + PayloadFormatIndicator = applicationMessage.PayloadFormatIndicator, + ResponseTopic = applicationMessage.ResponseTopic, + TopicAlias = applicationMessage.TopicAlias, + SubscriptionIdentifiers = applicationMessage.SubscriptionIdentifiers, + UserProperties = applicationMessage.UserProperties + }; - return packet; - } + return packet; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index 20dd59f49..4e30ec90c 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -5,809 +5,808 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter.V3 +namespace MQTTnet.Formatter.V3; + +public sealed class MqttV3PacketFormatter : IMqttPacketFormatter { - public sealed class MqttV3PacketFormatter : IMqttPacketFormatter - { - const int FixedHeaderSize = 1; + const int FixedHeaderSize = 1; + + static readonly MqttDisconnectPacket DisconnectPacket = new(); - static readonly MqttDisconnectPacket DisconnectPacket = new(); + readonly MqttBufferReader _bufferReader = new(); + readonly MqttBufferWriter _bufferWriter; + readonly MqttProtocolVersion _mqttProtocolVersion; - readonly MqttBufferReader _bufferReader = new(); - readonly MqttBufferWriter _bufferWriter; - readonly MqttProtocolVersion _mqttProtocolVersion; + public MqttV3PacketFormatter(MqttBufferWriter bufferWriter, MqttProtocolVersion mqttProtocolVersion) + { + _bufferWriter = bufferWriter; + _mqttProtocolVersion = mqttProtocolVersion; + } - public MqttV3PacketFormatter(MqttBufferWriter bufferWriter, MqttProtocolVersion mqttProtocolVersion) + public MqttPacket Decode(ReceivedMqttPacket receivedPacket) + { + if (receivedPacket.TotalLength == 0) { - _bufferWriter = bufferWriter; - _mqttProtocolVersion = mqttProtocolVersion; + return null; } - public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) + var controlPacketType = receivedPacket.FixedHeader >> 4; + if (controlPacketType is < 1 or > 14) { - if (receivedMqttPacket.TotalLength == 0) - { - return null; - } - - var controlPacketType = receivedMqttPacket.FixedHeader >> 4; - if (controlPacketType < 1 || controlPacketType > 14) - { - throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); - } - - switch ((MqttControlPacketType)controlPacketType) - { - case MqttControlPacketType.Publish: - return DecodePublishPacket(receivedMqttPacket); - case MqttControlPacketType.PubAck: - return DecodePubAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PubRec: - return DecodePubRecPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PubRel: - return DecodePubRelPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PubComp: - return DecodePubCompPacket(receivedMqttPacket.Body); - - case MqttControlPacketType.PingReq: - return MqttPingReqPacket.Instance; - case MqttControlPacketType.PingResp: - return MqttPingRespPacket.Instance; - - case MqttControlPacketType.Connect: - return DecodeConnectPacket(receivedMqttPacket.Body); - case MqttControlPacketType.ConnAck: - if (_mqttProtocolVersion == MqttProtocolVersion.V311) - { - return DecodeConnAckPacketV311(receivedMqttPacket.Body); - } - - return DecodeConnAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Disconnect: - return DisconnectPacket; - - case MqttControlPacketType.Subscribe: - return DecodeSubscribePacket(receivedMqttPacket.Body); - case MqttControlPacketType.SubAck: - return DecodeSubAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Unsubscribe: - return DecodeUnsubscribePacket(receivedMqttPacket.Body); - case MqttControlPacketType.UnsubAck: - return DecodeUnsubAckPacket(receivedMqttPacket.Body); - - default: - throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); - } + throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); } - public MqttPacketBuffer Encode(MqttPacket packet) + switch ((MqttControlPacketType)controlPacketType) { - ArgumentNullException.ThrowIfNull(packet); - - // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) - _bufferWriter.Reset(5); - _bufferWriter.Seek(5); - - var fixedHeader = EncodePacket(packet, _bufferWriter); - var remainingLength = (uint)(_bufferWriter.Length - 5); + case MqttControlPacketType.Publish: + return DecodePublishPacket(receivedPacket); + case MqttControlPacketType.PubAck: + return DecodePubAckPacket(receivedPacket.Body); + case MqttControlPacketType.PubRec: + return DecodePubRecPacket(receivedPacket.Body); + case MqttControlPacketType.PubRel: + return DecodePubRelPacket(receivedPacket.Body); + case MqttControlPacketType.PubComp: + return DecodePubCompPacket(receivedPacket.Body); - ReadOnlySequence payload = default; - if (packet is MqttPublishPacket publishPacket) - { - payload = publishPacket.Payload; - remainingLength += (uint)payload.Length; - } - - var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); + case MqttControlPacketType.PingReq: + return MqttPingReqPacket.Instance; + case MqttControlPacketType.PingResp: + return MqttPingRespPacket.Instance; - var headerSize = FixedHeaderSize + remainingLengthSize; - var headerOffset = 5 - headerSize; + case MqttControlPacketType.Connect: + return DecodeConnectPacket(receivedPacket.Body); + case MqttControlPacketType.ConnAck: + if (_mqttProtocolVersion == MqttProtocolVersion.V311) + { + return DecodeConnAckPacketV311(receivedPacket.Body); + } - // Position cursor on correct offset on beginning of array (has leading 0x0) - _bufferWriter.Seek(headerOffset); - _bufferWriter.WriteByte(fixedHeader); - _bufferWriter.WriteVariableByteInteger(remainingLength); + return DecodeConnAckPacket(receivedPacket.Body); + case MqttControlPacketType.Disconnect: + return DisconnectPacket; - var firstSegment = new ArraySegment(_bufferWriter.GetBuffer(), headerOffset, _bufferWriter.Length - headerOffset); + case MqttControlPacketType.Subscribe: + return DecodeSubscribePacket(receivedPacket.Body); + case MqttControlPacketType.SubAck: + return DecodeSubAckPacket(receivedPacket.Body); + case MqttControlPacketType.Unsubscribe: + return DecodeUnsubscribePacket(receivedPacket.Body); + case MqttControlPacketType.UnsubAck: + return DecodeUnsubAckPacket(receivedPacket.Body); - return payload.Length == 0 - ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payload); + case MqttControlPacketType.Auth: + default: + throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); } + } - MqttPacket DecodeConnAckPacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); - - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + public MqttPacketBuffer Encode(MqttPacket packet) + { + ArgumentNullException.ThrowIfNull(packet); - var packet = new MqttConnAckPacket(); + // Leave enough headspace for max header size (fixed + 4 variable remaining length = 5 bytes) + _bufferWriter.Reset(5); + _bufferWriter.Seek(5); - _bufferReader.ReadByte(); // Reserved. - packet.ReturnCode = (MqttConnectReturnCode)_bufferReader.ReadByte(); + var fixedHeader = EncodePacket(packet, _bufferWriter); + var remainingLength = (uint)(_bufferWriter.Length - 5); - return packet; + ReadOnlySequence payload = default; + if (packet is MqttPublishPacket publishPacket) + { + payload = publishPacket.Payload; + remainingLength += (uint)payload.Length; } - MqttPacket DecodeConnAckPacketV311(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + var headerSize = FixedHeaderSize + remainingLengthSize; + var headerOffset = 5 - headerSize; - var packet = new MqttConnAckPacket(); + // Position cursor on correct offset on beginning of array (has leading 0x0) + _bufferWriter.Seek(headerOffset); + _bufferWriter.WriteByte(fixedHeader); + _bufferWriter.WriteVariableByteInteger(remainingLength); - var acknowledgeFlags = _bufferReader.ReadByte(); + var firstSegment = new ArraySegment(_bufferWriter.GetBuffer(), headerOffset, _bufferWriter.Length - headerOffset); - packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; - packet.ReturnCode = (MqttConnectReturnCode)_bufferReader.ReadByte(); + return payload.Length == 0 + ? new MqttPacketBuffer(firstSegment) + : new MqttPacketBuffer(firstSegment, payload); + } - return packet; - } + MqttConnAckPacket DecodeConnAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - MqttPacket DecodeConnectPacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + var packet = new MqttConnAckPacket(); - var protocolName = _bufferReader.ReadString(); - var protocolVersion = _bufferReader.ReadByte(); + _bufferReader.ReadByte(); // Reserved. + packet.ReturnCode = (MqttConnectReturnCode)_bufferReader.ReadByte(); - if (protocolName != "MQTT" && protocolName != "MQIsdp") - { - throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3."); - } + return packet; + } - var tryPrivate = (protocolVersion & 0x80) > 0; - protocolVersion &= 0x7F; + MqttConnAckPacket DecodeConnAckPacketV311(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - if (protocolVersion != 3 && protocolVersion != 4) - { - throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3."); - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - var packet = new MqttConnectPacket - { - TryPrivate = tryPrivate - }; + var packet = new MqttConnAckPacket(); - var connectFlags = _bufferReader.ReadByte(); - if ((connectFlags & 0x1) > 0) - { - throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0."); - } + var acknowledgeFlags = _bufferReader.ReadByte(); - packet.CleanSession = (connectFlags & 0x2) > 0; + packet.IsSessionPresent = (acknowledgeFlags & 0x1) > 0; + packet.ReturnCode = (MqttConnectReturnCode)_bufferReader.ReadByte(); - var willFlag = (connectFlags & 0x4) > 0; - var willQoS = (connectFlags & 0x18) >> 3; - var willRetain = (connectFlags & 0x20) > 0; - var passwordFlag = (connectFlags & 0x40) > 0; - var usernameFlag = (connectFlags & 0x80) > 0; + return packet; + } - packet.KeepAlivePeriod = _bufferReader.ReadTwoByteInteger(); - packet.ClientId = _bufferReader.ReadString(); + MqttConnectPacket DecodeConnectPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - if (willFlag) - { - packet.WillFlag = true; - packet.WillQoS = (MqttQualityOfServiceLevel)willQoS; - packet.WillRetain = willRetain; + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - packet.WillTopic = _bufferReader.ReadString(); - packet.WillMessage = _bufferReader.ReadBinaryData(); - } + var protocolName = _bufferReader.ReadString(); + var protocolVersion = _bufferReader.ReadByte(); - if (usernameFlag) - { - packet.Username = _bufferReader.ReadString(); - } + if (protocolName != "MQTT" && protocolName != "MQIsdp") + { + throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3."); + } - if (passwordFlag) - { - packet.Password = _bufferReader.ReadBinaryData(); - } + var tryPrivate = (protocolVersion & 0x80) > 0; + protocolVersion &= 0x7F; - ValidateConnectPacket(packet); - return packet; + if (protocolVersion != 3 && protocolVersion != 4) + { + throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3."); } - MqttPacket DecodePubAckPacket(ArraySegment body) + var packet = new MqttConnectPacket { - ThrowIfBodyIsEmpty(body); - - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + TryPrivate = tryPrivate + }; - return new MqttPubAckPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + var connectFlags = _bufferReader.ReadByte(); + if ((connectFlags & 0x1) > 0) + { + throw new MqttProtocolViolationException("The first bit of the Connect Flags must be set to 0."); } - MqttPacket DecodePubCompPacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + packet.CleanSession = (connectFlags & 0x2) > 0; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + var willFlag = (connectFlags & 0x4) > 0; + var willQoS = (connectFlags & 0x18) >> 3; + var willRetain = (connectFlags & 0x20) > 0; + var passwordFlag = (connectFlags & 0x40) > 0; + var usernameFlag = (connectFlags & 0x80) > 0; - return new MqttPubCompPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + packet.KeepAlivePeriod = _bufferReader.ReadTwoByteInteger(); + packet.ClientId = _bufferReader.ReadString(); + + if (willFlag) + { + packet.WillFlag = true; + packet.WillQoS = (MqttQualityOfServiceLevel)willQoS; + packet.WillRetain = willRetain; + + packet.WillTopic = _bufferReader.ReadString(); + packet.WillMessage = _bufferReader.ReadBinaryData(); } - MqttPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) + if (usernameFlag) { - ThrowIfBodyIsEmpty(receivedMqttPacket.Body); + packet.Username = _bufferReader.ReadString(); + } - _bufferReader.SetBuffer(receivedMqttPacket.Body.Array, receivedMqttPacket.Body.Offset, receivedMqttPacket.Body.Count); + if (passwordFlag) + { + packet.Password = _bufferReader.ReadBinaryData(); + } - var retain = (receivedMqttPacket.FixedHeader & 0x1) > 0; - var qualityOfServiceLevel = (MqttQualityOfServiceLevel)((receivedMqttPacket.FixedHeader >> 1) & 0x3); - var dup = (receivedMqttPacket.FixedHeader & 0x8) > 0; + ValidateConnectPacket(packet); + return packet; + } - var topic = _bufferReader.ReadString(); + MqttPubAckPacket DecodePubAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - ushort packetIdentifier = 0; - if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) - { - packetIdentifier = _bufferReader.ReadTwoByteInteger(); - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - var packet = new MqttPublishPacket - { - PacketIdentifier = packetIdentifier, - Retain = retain, - Topic = topic, - QualityOfServiceLevel = qualityOfServiceLevel, - Dup = dup - }; + return new MqttPubAckPacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + } - if (!_bufferReader.EndOfStream) - { - packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); - } + MqttPubCompPacket DecodePubCompPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - return packet; - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodePubRecPacket(ArraySegment body) + return new MqttPubCompPacket { - ThrowIfBodyIsEmpty(body); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + MqttPublishPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) + { + ThrowIfBodyIsEmpty(receivedMqttPacket.Body); - return new MqttPubRecPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; - } + _bufferReader.SetBuffer(receivedMqttPacket.Body.Array, receivedMqttPacket.Body.Offset, receivedMqttPacket.Body.Count); - MqttPacket DecodePubRelPacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + var retain = (receivedMqttPacket.FixedHeader & 0x1) > 0; + var qualityOfServiceLevel = (MqttQualityOfServiceLevel)((receivedMqttPacket.FixedHeader >> 1) & 0x3); + var dup = (receivedMqttPacket.FixedHeader & 0x8) > 0; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + var topic = _bufferReader.ReadString(); - return new MqttPubRelPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + ushort packetIdentifier = 0; + if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + { + packetIdentifier = _bufferReader.ReadTwoByteInteger(); } - MqttPacket DecodeSubAckPacket(ArraySegment body) + var packet = new MqttPublishPacket { - ThrowIfBodyIsEmpty(body); + PacketIdentifier = packetIdentifier, + Retain = retain, + Topic = topic, + QualityOfServiceLevel = qualityOfServiceLevel, + Dup = dup + }; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + if (!_bufferReader.EndOfStream) + { + packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); + } - var packet = new MqttSubAckPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger(), - ReasonCodes = new List(_bufferReader.BytesLeft) - }; + return packet; + } - while (!_bufferReader.EndOfStream) - { - packet.ReasonCodes.Add((MqttSubscribeReasonCode)_bufferReader.ReadByte()); - } + MqttPubRecPacket DecodePubRecPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - return packet; - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodeSubscribePacket(ArraySegment body) + return new MqttPubRecPacket { - ThrowIfBodyIsEmpty(body); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + MqttPubRelPacket DecodePubRelPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - var packet = new MqttSubscribePacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - while (!_bufferReader.EndOfStream) - { - var topicFilter = new MqttTopicFilter - { - Topic = _bufferReader.ReadString(), - QualityOfServiceLevel = (MqttQualityOfServiceLevel)_bufferReader.ReadByte() - }; + return new MqttPubRelPacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + } - packet.TopicFilters.Add(topicFilter); - } + MqttSubAckPacket DecodeSubAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - return packet; - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodeUnsubAckPacket(ArraySegment body) + var packet = new MqttSubAckPacket { - ThrowIfBodyIsEmpty(body); - - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + PacketIdentifier = _bufferReader.ReadTwoByteInteger(), + ReasonCodes = new List(_bufferReader.BytesLeft) + }; - return new MqttUnsubAckPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + while (!_bufferReader.EndOfStream) + { + packet.ReasonCodes.Add((MqttSubscribeReasonCode)_bufferReader.ReadByte()); } - MqttPacket DecodeUnsubscribePacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + return packet; + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + MqttSubscribePacket DecodeSubscribePacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - var packet = new MqttUnsubscribePacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - while (!_bufferReader.EndOfStream) + var packet = new MqttSubscribePacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + + while (!_bufferReader.EndOfStream) + { + var topicFilter = new MqttTopicFilter { - packet.TopicFilters.Add(_bufferReader.ReadString()); - } + Topic = _bufferReader.ReadString(), + QualityOfServiceLevel = (MqttQualityOfServiceLevel)_bufferReader.ReadByte() + }; - return packet; + packet.TopicFilters.Add(topicFilter); } - byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttBufferWriter bufferWriter) - { - bufferWriter.WriteByte(0); // Reserved. - bufferWriter.WriteByte((byte)packet.ReturnCode); + return packet; + } - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); - } + MqttUnsubAckPacket DecodeUnsubAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - byte EncodeConnAckPacketV311(MqttConnAckPacket packet, MqttBufferWriter bufferWriter) + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + + return new MqttUnsubAckPacket { - byte connectAcknowledgeFlags = 0x0; - if (packet.IsSessionPresent) - { - connectAcknowledgeFlags |= 0x1; - } + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + } - bufferWriter.WriteByte(connectAcknowledgeFlags); - bufferWriter.WriteByte((byte)packet.ReturnCode); + MqttUnsubscribePacket DecodeUnsubscribePacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - byte EncodeConnectPacket(MqttConnectPacket packet, MqttBufferWriter bufferWriter) + var packet = new MqttUnsubscribePacket { - ValidateConnectPacket(packet); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - bufferWriter.WriteString("MQIsdp"); + while (!_bufferReader.EndOfStream) + { + packet.TopicFilters.Add(_bufferReader.ReadString()); + } - var protocolVersion = 3; - if (packet.TryPrivate) - { - protocolVersion |= 0x80; - } + return packet; + } - bufferWriter.WriteByte((byte)protocolVersion); + static byte EncodeConnAckPacket(MqttConnAckPacket packet, MqttBufferWriter bufferWriter) + { + bufferWriter.WriteByte(0); // Reserved. + bufferWriter.WriteByte((byte)packet.ReturnCode); - byte connectFlags = 0x0; - if (packet.CleanSession) - { - connectFlags |= 0x2; - } + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + } - if (packet.WillFlag) - { - connectFlags |= 0x4; - connectFlags |= (byte)((byte)packet.WillQoS << 3); + static byte EncodeConnAckPacketV311(MqttConnAckPacket packet, MqttBufferWriter bufferWriter) + { + byte connectAcknowledgeFlags = 0x0; + if (packet.IsSessionPresent) + { + connectAcknowledgeFlags |= 0x1; + } - if (packet.WillRetain) - { - connectFlags |= 0x20; - } - } + bufferWriter.WriteByte(connectAcknowledgeFlags); + bufferWriter.WriteByte((byte)packet.ReturnCode); - if (packet.Password != null && packet.Username == null) - { - throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); - } + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); + } - if (packet.Password != null) - { - connectFlags |= 0x40; - } + static byte EncodeConnectPacket(MqttConnectPacket packet, MqttBufferWriter bufferWriter) + { + ValidateConnectPacket(packet); - if (packet.Username != null) - { - connectFlags |= 0x80; - } + bufferWriter.WriteString("MQIsdp"); - bufferWriter.WriteByte(connectFlags); - bufferWriter.WriteTwoByteInteger(packet.KeepAlivePeriod); - bufferWriter.WriteString(packet.ClientId); + var protocolVersion = 3; + if (packet.TryPrivate) + { + protocolVersion |= 0x80; + } - if (packet.WillFlag) - { - bufferWriter.WriteString(packet.WillTopic); - bufferWriter.WriteBinary(packet.WillMessage); - } + bufferWriter.WriteByte((byte)protocolVersion); - if (packet.Username != null) - { - bufferWriter.WriteString(packet.Username); - } + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } + + if (packet.WillFlag) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillQoS << 3); - if (packet.Password != null) + if (packet.WillRetain) { - bufferWriter.WriteBinary(packet.Password); + connectFlags |= 0x20; } + } - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); + if (packet.Password != null && packet.Username == null) + { + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); } - byte EncodeConnectPacketV311(MqttConnectPacket packet, MqttBufferWriter bufferWriter) + if (packet.Password != null) { - ValidateConnectPacket(packet); + connectFlags |= 0x40; + } - bufferWriter.WriteString("MQTT"); + if (packet.Username != null) + { + connectFlags |= 0x80; + } - // 3.1.2.2 Protocol Level 4 - var protocolVersion = 4; + bufferWriter.WriteByte(connectFlags); + bufferWriter.WriteTwoByteInteger(packet.KeepAlivePeriod); + bufferWriter.WriteString(packet.ClientId); - if (packet.TryPrivate) - { - protocolVersion |= 0x80; - } + if (packet.WillFlag) + { + bufferWriter.WriteString(packet.WillTopic); + bufferWriter.WriteBinary(packet.WillMessage); + } - bufferWriter.WriteByte((byte)protocolVersion); + if (packet.Username != null) + { + bufferWriter.WriteString(packet.Username); + } - byte connectFlags = 0x0; - if (packet.CleanSession) - { - connectFlags |= 0x2; - } + if (packet.Password != null) + { + bufferWriter.WriteBinary(packet.Password); + } - if (packet.WillFlag) - { - connectFlags |= 0x4; - connectFlags |= (byte)((byte)packet.WillQoS << 3); + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); + } - if (packet.WillRetain) - { - connectFlags |= 0x20; - } - } + static byte EncodeConnectPacketV311(MqttConnectPacket packet, MqttBufferWriter bufferWriter) + { + ValidateConnectPacket(packet); - if (packet.Password != null && packet.Username == null) - { - throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); - } + bufferWriter.WriteString("MQTT"); - if (packet.Password != null) - { - connectFlags |= 0x40; - } + // 3.1.2.2 Protocol Level 4 + var protocolVersion = 4; - if (packet.Username != null) - { - connectFlags |= 0x80; - } + if (packet.TryPrivate) + { + protocolVersion |= 0x80; + } - bufferWriter.WriteByte(connectFlags); - bufferWriter.WriteTwoByteInteger(packet.KeepAlivePeriod); - bufferWriter.WriteString(packet.ClientId); + bufferWriter.WriteByte((byte)protocolVersion); - if (packet.WillFlag) - { - bufferWriter.WriteString(packet.WillTopic); - bufferWriter.WriteBinary(packet.WillMessage); - } + byte connectFlags = 0x0; + if (packet.CleanSession) + { + connectFlags |= 0x2; + } - if (packet.Username != null) - { - bufferWriter.WriteString(packet.Username); - } + if (packet.WillFlag) + { + connectFlags |= 0x4; + connectFlags |= (byte)((byte)packet.WillQoS << 3); - if (packet.Password != null) + if (packet.WillRetain) { - bufferWriter.WriteBinary(packet.Password); + connectFlags |= 0x20; } - - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); } - static byte EncodeEmptyPacket(MqttControlPacketType type) + if (packet.Password != null && packet.Username == null) { - return MqttBufferWriter.BuildFixedHeader(type); + throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22]."); } - byte EncodePacket(MqttPacket packet, MqttBufferWriter bufferWriter) + if (packet.Password != null) { - switch (packet) - { - case MqttConnectPacket connectPacket: - if (_mqttProtocolVersion == MqttProtocolVersion.V311) - { - return EncodeConnectPacketV311(connectPacket, bufferWriter); - } - - return EncodeConnectPacket(connectPacket, bufferWriter); - case MqttConnAckPacket connAckPacket: - if (_mqttProtocolVersion == MqttProtocolVersion.V311) - { - return EncodeConnAckPacketV311(connAckPacket, bufferWriter); - } - - return EncodeConnAckPacket(connAckPacket, bufferWriter); - case MqttDisconnectPacket _: - return EncodeEmptyPacket(MqttControlPacketType.Disconnect); - case MqttPingReqPacket _: - return EncodeEmptyPacket(MqttControlPacketType.PingReq); - case MqttPingRespPacket _: - return EncodeEmptyPacket(MqttControlPacketType.PingResp); - case MqttPublishPacket publishPacket: - return EncodePublishPacket(publishPacket, bufferWriter); - case MqttPubAckPacket pubAckPacket: - return EncodePubAckPacket(pubAckPacket, bufferWriter); - case MqttPubRecPacket pubRecPacket: - return EncodePubRecPacket(pubRecPacket, bufferWriter); - case MqttPubRelPacket pubRelPacket: - return EncodePubRelPacket(pubRelPacket, bufferWriter); - case MqttPubCompPacket pubCompPacket: - return EncodePubCompPacket(pubCompPacket, bufferWriter); - case MqttSubscribePacket subscribePacket: - return EncodeSubscribePacket(subscribePacket, bufferWriter); - case MqttSubAckPacket subAckPacket: - return EncodeSubAckPacket(subAckPacket, bufferWriter); - case MqttUnsubscribePacket unsubscribePacket: - return EncodeUnsubscribePacket(unsubscribePacket, bufferWriter); - case MqttUnsubAckPacket unsubAckPacket: - return EncodeUnsubAckPacket(unsubAckPacket, bufferWriter); - - default: - throw new MqttProtocolViolationException("Packet type invalid."); - } + connectFlags |= 0x40; } - static byte EncodePubAckPacket(MqttPubAckPacket packet, MqttBufferWriter bufferWriter) + if (packet.Username != null) { - if (packet.PacketIdentifier == 0) - { - throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); - } + connectFlags |= 0x80; + } - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + bufferWriter.WriteByte(connectFlags); + bufferWriter.WriteTwoByteInteger(packet.KeepAlivePeriod); + bufferWriter.WriteString(packet.ClientId); - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubAck); + if (packet.WillFlag) + { + bufferWriter.WriteString(packet.WillTopic); + bufferWriter.WriteBinary(packet.WillMessage); } - static byte EncodePubCompPacket(MqttPubCompPacket packet, MqttBufferWriter bufferWriter) + if (packet.Username != null) { - if (packet.PacketIdentifier == 0) - { - throw new MqttProtocolViolationException("PubComp packet has no packet identifier."); - } - - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); - - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubComp); + bufferWriter.WriteString(packet.Username); } - static byte EncodePublishPacket(MqttPublishPacket packet, MqttBufferWriter bufferWriter) + if (packet.Password != null) { - ValidatePublishPacket(packet); + bufferWriter.WriteBinary(packet.Password); + } - bufferWriter.WriteString(packet.Topic); + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); + } - if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) - { - if (packet.PacketIdentifier == 0) + static byte EncodeEmptyPacket(MqttControlPacketType type) + { + return MqttBufferWriter.BuildFixedHeader(type); + } + + byte EncodePacket(MqttPacket packet, MqttBufferWriter bufferWriter) + { + switch (packet) + { + case MqttConnectPacket connectPacket: + if (_mqttProtocolVersion == MqttProtocolVersion.V311) { - throw new MqttProtocolViolationException("Publish packet has no packet identifier."); + return EncodeConnectPacketV311(connectPacket, bufferWriter); } - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); - } - else - { - if (packet.PacketIdentifier > 0) + return EncodeConnectPacket(connectPacket, bufferWriter); + case MqttConnAckPacket connAckPacket: + if (_mqttProtocolVersion == MqttProtocolVersion.V311) { - throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5]."); + return EncodeConnAckPacketV311(connAckPacket, bufferWriter); } - } - // The payload is the past part of the packet. But it is not added here in order to keep - // memory allocation low. - - byte fixedHeader = 0; + return EncodeConnAckPacket(connAckPacket, bufferWriter); + case MqttDisconnectPacket _: + return EncodeEmptyPacket(MqttControlPacketType.Disconnect); + case MqttPingReqPacket _: + return EncodeEmptyPacket(MqttControlPacketType.PingReq); + case MqttPingRespPacket _: + return EncodeEmptyPacket(MqttControlPacketType.PingResp); + case MqttPublishPacket publishPacket: + return EncodePublishPacket(publishPacket, bufferWriter); + case MqttPubAckPacket pubAckPacket: + return EncodePubAckPacket(pubAckPacket, bufferWriter); + case MqttPubRecPacket pubRecPacket: + return EncodePubRecPacket(pubRecPacket, bufferWriter); + case MqttPubRelPacket pubRelPacket: + return EncodePubRelPacket(pubRelPacket, bufferWriter); + case MqttPubCompPacket pubCompPacket: + return EncodePubCompPacket(pubCompPacket, bufferWriter); + case MqttSubscribePacket subscribePacket: + return EncodeSubscribePacket(subscribePacket, bufferWriter); + case MqttSubAckPacket subAckPacket: + return EncodeSubAckPacket(subAckPacket, bufferWriter); + case MqttUnsubscribePacket unsubscribePacket: + return EncodeUnsubscribePacket(unsubscribePacket, bufferWriter); + case MqttUnsubAckPacket unsubAckPacket: + return EncodeUnsubAckPacket(unsubAckPacket, bufferWriter); + + default: + throw new MqttProtocolViolationException("Packet type invalid."); + } + } - if (packet.Retain) - { - fixedHeader |= 0x01; - } + static byte EncodePubAckPacket(MqttPubAckPacket packet, MqttBufferWriter bufferWriter) + { + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("PubAck packet has no packet identifier."); + } - fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1); + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); - if (packet.Dup) - { - fixedHeader |= 0x08; - } + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubAck); + } - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); + static byte EncodePubCompPacket(MqttPubCompPacket packet, MqttBufferWriter bufferWriter) + { + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("PubComp packet has no packet identifier."); } - static byte EncodePubRecPacket(MqttPubRecPacket packet, MqttBufferWriter bufferWriter) + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubComp); + } + + static byte EncodePublishPacket(MqttPublishPacket packet, MqttBufferWriter bufferWriter) + { + ValidatePublishPacket(packet); + + bufferWriter.WriteString(packet.Topic); + + if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { if (packet.PacketIdentifier == 0) { - throw new MqttProtocolViolationException("PubRec packet has no packet identifier."); + throw new MqttProtocolViolationException("Publish packet has no packet identifier."); } bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); - - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubRec); } - - static byte EncodePubRelPacket(MqttPubRelPacket packet, MqttBufferWriter bufferWriter) + else { - if (packet.PacketIdentifier == 0) + if (packet.PacketIdentifier > 0) { - throw new MqttProtocolViolationException("PubRel packet has no packet identifier."); + throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5]."); } + } - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + // The payload is the past part of the packet. But it is not added here in order to keep + // memory allocation low. - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); - } + byte fixedHeader = 0; - static byte EncodeSubAckPacket(MqttSubAckPacket packet, MqttBufferWriter bufferWriter) + if (packet.Retain) { - if (packet.PacketIdentifier == 0) - { - throw new MqttProtocolViolationException("SubAck packet has no packet identifier."); - } + fixedHeader |= 0x01; + } - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1); - if (packet.ReasonCodes.Any()) - { - foreach (var packetSubscribeReturnCode in packet.ReasonCodes) - { - if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS0) - { - bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS0); - } - else if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS1) - { - bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS1); - } - else if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS2) - { - bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS2); - } - else - { - bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.Failure); - } - } - } + if (packet.Dup) + { + fixedHeader |= 0x08; + } - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.SubAck); + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader); + } + + static byte EncodePubRecPacket(MqttPubRecPacket packet, MqttBufferWriter bufferWriter) + { + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("PubRec packet has no packet identifier."); } - static byte EncodeSubscribePacket(MqttSubscribePacket packet, MqttBufferWriter bufferWriter) + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubRec); + } + + static byte EncodePubRelPacket(MqttPubRelPacket packet, MqttBufferWriter bufferWriter) + { + if (packet.PacketIdentifier == 0) { - if (!packet.TopicFilters.Any()) - { - throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); - } + throw new MqttProtocolViolationException("PubRel packet has no packet identifier."); + } - if (packet.PacketIdentifier == 0) - { - throw new MqttProtocolViolationException("Subscribe packet has no packet identifier."); - } + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02); + } - if (packet.TopicFilters?.Count > 0) + static byte EncodeSubAckPacket(MqttSubAckPacket packet, MqttBufferWriter bufferWriter) + { + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("SubAck packet has no packet identifier."); + } + + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + + if (packet.ReasonCodes.Count != 0) + { + foreach (var packetSubscribeReturnCode in packet.ReasonCodes) { - foreach (var topicFilter in packet.TopicFilters) + if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS0) { - bufferWriter.WriteString(topicFilter.Topic); - bufferWriter.WriteByte((byte)topicFilter.QualityOfServiceLevel); + bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS0); + } + else if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS1) + { + bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS1); + } + else if (packetSubscribeReturnCode == MqttSubscribeReasonCode.GrantedQoS2) + { + bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.SuccessMaximumQoS2); + } + else + { + bufferWriter.WriteByte((byte)MqttSubscribeReturnCode.Failure); } } - - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); } - static byte EncodeUnsubAckPacket(MqttUnsubAckPacket packet, MqttBufferWriter bufferWriter) + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.SubAck); + } + + static byte EncodeSubscribePacket(MqttSubscribePacket packet, MqttBufferWriter bufferWriter) + { + if (packet.TopicFilters.Count == 0) { - if (packet.PacketIdentifier == 0) - { - throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier."); - } + throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); + } - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("Subscribe packet has no packet identifier."); } - static byte EncodeUnsubscribePacket(MqttUnsubscribePacket packet, MqttBufferWriter bufferWriter) + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + + if (packet.TopicFilters?.Count > 0) { - if (!packet.TopicFilters.Any()) + foreach (var topicFilter in packet.TopicFilters) { - throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); + bufferWriter.WriteString(topicFilter.Topic); + bufferWriter.WriteByte((byte)topicFilter.QualityOfServiceLevel); } + } - if (packet.PacketIdentifier == 0) - { - throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier."); - } + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02); + } - bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + static byte EncodeUnsubAckPacket(MqttUnsubAckPacket packet, MqttBufferWriter bufferWriter) + { + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier."); + } - if (packet.TopicFilters?.Any() == true) - { - foreach (var topicFilter in packet.TopicFilters) - { - bufferWriter.WriteString(topicFilter); - } - } + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck); + } - return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02); + static byte EncodeUnsubscribePacket(MqttUnsubscribePacket packet, MqttBufferWriter bufferWriter) + { + if (packet.TopicFilters.Count == 0) + { + throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); + } + + if (packet.PacketIdentifier == 0) + { + throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier."); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ThrowIfBodyIsEmpty(ArraySegment body) + bufferWriter.WriteTwoByteInteger(packet.PacketIdentifier); + + if (packet.TopicFilters?.Count > 0) { - if (body.Count == 0) + foreach (var topicFilter in packet.TopicFilters) { - throw new MqttProtocolViolationException("Data from the body is required but not present."); + bufferWriter.WriteString(topicFilter); } } - void ValidateConnectPacket(MqttConnectPacket packet) + return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ThrowIfBodyIsEmpty(ArraySegment body) + { + if (body.Count == 0) { - ArgumentNullException.ThrowIfNull(packet); + throw new MqttProtocolViolationException("Data from the body is required but not present."); + } + } - if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) - { - throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); - } + static void ValidateConnectPacket(MqttConnectPacket packet) + { + ArgumentNullException.ThrowIfNull(packet); + + if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) + { + throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7]."); } + } - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - static void ValidatePublishPacket(MqttPublishPacket packet) + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + static void ValidatePublishPacket(MqttPublishPacket packet) + { + if (packet.QualityOfServiceLevel == 0 && packet.Dup) { - if (packet.QualityOfServiceLevel == 0 && packet.Dup) - { - throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); - } + throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2]."); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index 7f6a11f1a..8997ae8fe 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -9,774 +9,756 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter.V5 +namespace MQTTnet.Formatter.V5; + +public sealed class MqttV5PacketDecoder { - public sealed class MqttV5PacketDecoder + readonly MqttBufferReader _bufferReader = new(); + + public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) { - readonly MqttBufferReader _bufferReader = new(); + if (receivedMqttPacket.TotalLength == 0) + { + return null; + } - public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) + var controlPacketType = receivedMqttPacket.FixedHeader >> 4; + if (controlPacketType < 1) { - if (receivedMqttPacket.TotalLength == 0) - { - return null; - } + throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); + } - var controlPacketType = receivedMqttPacket.FixedHeader >> 4; - if (controlPacketType < 1) - { - throw new MqttProtocolViolationException($"The packet type is invalid ({controlPacketType})."); - } + return (MqttControlPacketType)controlPacketType switch + { + MqttControlPacketType.Connect => DecodeConnectPacket(receivedMqttPacket.Body), + MqttControlPacketType.ConnAck => DecodeConnAckPacket(receivedMqttPacket.Body), + MqttControlPacketType.Disconnect => DecodeDisconnectPacket(receivedMqttPacket.Body), + MqttControlPacketType.Publish => DecodePublishPacket(receivedMqttPacket.FixedHeader, receivedMqttPacket.Body), + MqttControlPacketType.PubAck => DecodePubAckPacket(receivedMqttPacket.Body), + MqttControlPacketType.PubRec => DecodePubRecPacket(receivedMqttPacket.Body), + MqttControlPacketType.PubRel => DecodePubRelPacket(receivedMqttPacket.Body), + MqttControlPacketType.PubComp => DecodePubCompPacket(receivedMqttPacket.Body), + MqttControlPacketType.PingReq => MqttPingReqPacket.Instance, + MqttControlPacketType.PingResp => MqttPingRespPacket.Instance, + MqttControlPacketType.Subscribe => DecodeSubscribePacket(receivedMqttPacket.Body), + MqttControlPacketType.SubAck => DecodeSubAckPacket(receivedMqttPacket.Body), + MqttControlPacketType.Unsubscribe => DecodeUnsubscribePacket(receivedMqttPacket.Body), + MqttControlPacketType.UnsubAck => DecodeUnsubAckPacket(receivedMqttPacket.Body), + MqttControlPacketType.Auth => DecodeAuthPacket(receivedMqttPacket.Body), + _ => throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported.") + }; + } - switch ((MqttControlPacketType)controlPacketType) - { - case MqttControlPacketType.Connect: - return DecodeConnectPacket(receivedMqttPacket.Body); - case MqttControlPacketType.ConnAck: - return DecodeConnAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Disconnect: - return DecodeDisconnectPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Publish: - return DecodePublishPacket(receivedMqttPacket.FixedHeader, receivedMqttPacket.Body); - case MqttControlPacketType.PubAck: - return DecodePubAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PubRec: - return DecodePubRecPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PubRel: - return DecodePubRelPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PubComp: - return DecodePubCompPacket(receivedMqttPacket.Body); - case MqttControlPacketType.PingReq: - return MqttPingReqPacket.Instance; - case MqttControlPacketType.PingResp: - return MqttPingRespPacket.Instance; - case MqttControlPacketType.Subscribe: - return DecodeSubscribePacket(receivedMqttPacket.Body); - case MqttControlPacketType.SubAck: - return DecodeSubAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Unsubscribe: - return DecodeUnsubscribePacket(receivedMqttPacket.Body); - case MqttControlPacketType.UnsubAck: - return DecodeUnsubAckPacket(receivedMqttPacket.Body); - case MqttControlPacketType.Auth: - return DecodeAuthPacket(receivedMqttPacket.Body); + MqttAuthPacket DecodeAuthPacket(ArraySegment body) + { + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - default: - throw new MqttProtocolViolationException($"Packet type ({controlPacketType}) not supported."); - } - } + var packet = new MqttAuthPacket(); - MqttPacket DecodeAuthPacket(ArraySegment body) + // MQTT spec: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. + // In this case the AUTH has a Remaining Length of 0. + if (_bufferReader.EndOfStream) { - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + packet.ReasonCode = MqttAuthenticateReasonCode.Success; + return packet; + } - var packet = new MqttAuthPacket(); + packet.ReasonCode = (MqttAuthenticateReasonCode)_bufferReader.ReadByte(); - // MQTT spec: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. - // In this case the AUTH has a Remaining Length of 0. - if (_bufferReader.EndOfStream) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) { - packet.ReasonCode = MqttAuthenticateReasonCode.Success; - return packet; + packet.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); } - - packet.ReasonCode = (MqttAuthenticateReasonCode)_bufferReader.ReadByte(); - - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) - { - packet.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) - { - packet.AuthenticationData = propertiesReader.ReadAuthenticationData(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttAuthPacket)); - } + packet.AuthenticationData = propertiesReader.ReadAuthenticationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.ReasonString = propertiesReader.ReadReasonString(); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttAuthPacket)); } - - packet.UserProperties = propertiesReader.CollectedUserProperties; - - return packet; } - MqttPacket DecodeConnAckPacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + packet.UserProperties = propertiesReader.CollectedUserProperties; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + return packet; + } - var acknowledgeFlags = _bufferReader.ReadByte(); + MqttConnAckPacket DecodeConnAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - var packet = new MqttConnAckPacket - { - IsSessionPresent = (acknowledgeFlags & 0x1) > 0, - ReasonCode = (MqttConnectReasonCode)_bufferReader.ReadByte(), - // indicate that a feature is available. - // Set all default values according to specification. When they are missing the often - RetainAvailable = true, - SharedSubscriptionAvailable = true, - SubscriptionIdentifiersAvailable = true, - WildcardSubscriptionAvailable = true, - // Absence indicates max QoS level. - MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce - }; + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - // Also set the return code of MQTT 3.1.1 for backward compatibility and debugging purposes. - packet.ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(packet.ReasonCode); + var acknowledgeFlags = _bufferReader.ReadByte(); - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + var packet = new MqttConnAckPacket + { + IsSessionPresent = (acknowledgeFlags & 0x1) > 0, + ReasonCode = (MqttConnectReasonCode)_bufferReader.ReadByte(), + // indicate that a feature is available. + // Set all default values according to specification. When they are missing the often + RetainAvailable = true, + SharedSubscriptionAvailable = true, + SubscriptionIdentifiersAvailable = true, + WildcardSubscriptionAvailable = true, + // Absence indicates max QoS level. + MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce + }; + + // Also set the return code of MQTT 3.1.1 for backward compatibility and debugging purposes. + packet.ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(packet.ReasonCode); + + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) - { - packet.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) - { - packet.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) - { - packet.AuthenticationData = propertiesReader.ReadAuthenticationData(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RetainAvailable) - { - packet.RetainAvailable = propertiesReader.ReadRetainAvailable(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReceiveMaximum) - { - packet.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumQoS) - { - packet.MaximumQoS = propertiesReader.ReadMaximumQoS(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AssignedClientIdentifier) - { - packet.AssignedClientIdentifier = propertiesReader.ReadAssignedClientIdentifier(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAliasMaximum) - { - packet.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumPacketSize) - { - packet.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.WildcardSubscriptionAvailable) - { - packet.WildcardSubscriptionAvailable = propertiesReader.ReadWildcardSubscriptionAvailable(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifiersAvailable) - { - packet.SubscriptionIdentifiersAvailable = propertiesReader.ReadSubscriptionIdentifiersAvailable(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SharedSubscriptionAvailable) - { - packet.SharedSubscriptionAvailable = propertiesReader.ReadSharedSubscriptionAvailable(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerKeepAlive) - { - packet.ServerKeepAlive = propertiesReader.ReadServerKeepAlive(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ResponseInformation) - { - packet.ResponseInformation = propertiesReader.ReadResponseInformation(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerReference) - { - packet.ServerReference = propertiesReader.ReadServerReference(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttConnAckPacket)); - } + packet.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) + { + packet.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) + { + packet.AuthenticationData = propertiesReader.ReadAuthenticationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RetainAvailable) + { + packet.RetainAvailable = propertiesReader.ReadRetainAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReceiveMaximum) + { + packet.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumQoS) + { + packet.MaximumQoS = propertiesReader.ReadMaximumQoS(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AssignedClientIdentifier) + { + packet.AssignedClientIdentifier = propertiesReader.ReadAssignedClientIdentifier(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAliasMaximum) + { + packet.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) + { + packet.ReasonString = propertiesReader.ReadReasonString(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumPacketSize) + { + packet.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.WildcardSubscriptionAvailable) + { + packet.WildcardSubscriptionAvailable = propertiesReader.ReadWildcardSubscriptionAvailable(); } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifiersAvailable) + { + packet.SubscriptionIdentifiersAvailable = propertiesReader.ReadSubscriptionIdentifiersAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SharedSubscriptionAvailable) + { + packet.SharedSubscriptionAvailable = propertiesReader.ReadSharedSubscriptionAvailable(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerKeepAlive) + { + packet.ServerKeepAlive = propertiesReader.ReadServerKeepAlive(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ResponseInformation) + { + packet.ResponseInformation = propertiesReader.ReadResponseInformation(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerReference) + { + packet.ServerReference = propertiesReader.ReadServerReference(); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttConnAckPacket)); + } + } - packet.UserProperties = propertiesReader.CollectedUserProperties; + packet.UserProperties = propertiesReader.CollectedUserProperties; - return packet; - } + return packet; + } + + MqttConnectPacket DecodeConnectPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); + + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodeConnectPacket(ArraySegment body) + var packet = new MqttConnectPacket { - ThrowIfBodyIsEmpty(body); + // If the Request Problem Information is absent, the value of 1 is used. + RequestProblemInformation = true + }; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + var protocolName = _bufferReader.ReadString(); + var protocolVersion = _bufferReader.ReadByte(); - var packet = new MqttConnectPacket - { - // If the Request Problem Information is absent, the value of 1 is used. - RequestProblemInformation = true - }; + if (protocolName != "MQTT" && protocolVersion != 5) + { + throw new MqttProtocolViolationException("MQTT protocol name and version do not match MQTT v5."); + } - var protocolName = _bufferReader.ReadString(); - var protocolVersion = _bufferReader.ReadByte(); + var connectFlags = _bufferReader.ReadByte(); - if (protocolName != "MQTT" && protocolVersion != 5) - { - throw new MqttProtocolViolationException("MQTT protocol name and version do not match MQTT v5."); - } + var cleanSessionFlag = (connectFlags & 0x02) > 0; + var willMessageFlag = (connectFlags & 0x04) > 0; + var willMessageQoS = (byte)((connectFlags >> 3) & 3); + var willMessageRetainFlag = (connectFlags & 0x20) > 0; + var passwordFlag = (connectFlags & 0x40) > 0; + var usernameFlag = (connectFlags & 0x80) > 0; - var connectFlags = _bufferReader.ReadByte(); + packet.CleanSession = cleanSessionFlag; - var cleanSessionFlag = (connectFlags & 0x02) > 0; - var willMessageFlag = (connectFlags & 0x04) > 0; - var willMessageQoS = (byte)((connectFlags >> 3) & 3); - var willMessageRetainFlag = (connectFlags & 0x20) > 0; - var passwordFlag = (connectFlags & 0x40) > 0; - var usernameFlag = (connectFlags & 0x80) > 0; + if (willMessageFlag) + { + packet.WillFlag = true; + packet.WillQoS = (MqttQualityOfServiceLevel)willMessageQoS; + packet.WillRetain = willMessageRetainFlag; + } - packet.CleanSession = cleanSessionFlag; + packet.KeepAlivePeriod = _bufferReader.ReadTwoByteInteger(); - if (willMessageFlag) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) + { + packet.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) + { + packet.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) + { + packet.AuthenticationData = propertiesReader.ReadAuthenticationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReceiveMaximum) + { + packet.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAliasMaximum) + { + packet.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumPacketSize) + { + packet.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RequestResponseInformation) + { + packet.RequestResponseInformation = propertiesReader.RequestResponseInformation(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RequestProblemInformation) + { + packet.RequestProblemInformation = propertiesReader.RequestProblemInformation(); + } + else { - packet.WillFlag = true; - packet.WillQoS = (MqttQualityOfServiceLevel)willMessageQoS; - packet.WillRetain = willMessageRetainFlag; + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttConnectPacket)); } + } + + packet.UserProperties = propertiesReader.CollectedUserProperties; - packet.KeepAlivePeriod = _bufferReader.ReadTwoByteInteger(); + packet.ClientId = _bufferReader.ReadString(); - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + if (packet.WillFlag) + { + var willPropertiesReader = new MqttV5PropertiesReader(_bufferReader); + + while (willPropertiesReader.MoveNext()) { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) + if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.PayloadFormatIndicator) { - packet.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); + packet.WillPayloadFormatIndicator = willPropertiesReader.ReadPayloadFormatIndicator(); } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationMethod) + else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.MessageExpiryInterval) { - packet.AuthenticationMethod = propertiesReader.ReadAuthenticationMethod(); + packet.WillMessageExpiryInterval = willPropertiesReader.ReadMessageExpiryInterval(); } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.AuthenticationData) + else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.ResponseTopic) { - packet.AuthenticationData = propertiesReader.ReadAuthenticationData(); + packet.WillResponseTopic = willPropertiesReader.ReadResponseTopic(); } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReceiveMaximum) + else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.CorrelationData) { - packet.ReceiveMaximum = propertiesReader.ReadReceiveMaximum(); + packet.WillCorrelationData = willPropertiesReader.ReadCorrelationData(); } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAliasMaximum) + else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.ContentType) { - packet.TopicAliasMaximum = propertiesReader.ReadTopicAliasMaximum(); + packet.WillContentType = willPropertiesReader.ReadContentType(); } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MaximumPacketSize) + else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.WillDelayInterval) { - packet.MaximumPacketSize = propertiesReader.ReadMaximumPacketSize(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RequestResponseInformation) - { - packet.RequestResponseInformation = propertiesReader.RequestResponseInformation(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.RequestProblemInformation) - { - packet.RequestProblemInformation = propertiesReader.RequestProblemInformation(); + packet.WillDelayInterval = willPropertiesReader.ReadWillDelayInterval(); } else { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttConnectPacket)); + willPropertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPublishPacket)); } } - packet.UserProperties = propertiesReader.CollectedUserProperties; + packet.WillTopic = _bufferReader.ReadString(); + packet.WillMessage = _bufferReader.ReadBinaryData(); + packet.WillUserProperties = willPropertiesReader.CollectedUserProperties; + } + + if (usernameFlag) + { + packet.Username = _bufferReader.ReadString(); + } + + if (passwordFlag) + { + packet.Password = _bufferReader.ReadBinaryData(); + } - packet.ClientId = _bufferReader.ReadString(); + return packet; + } - if (packet.WillFlag) + MqttDisconnectPacket DecodeDisconnectPacket(ArraySegment body) + { + // From RFC: 3.14.2.1 Disconnect Reason Code + // Byte 1 in the Variable Header is the Disconnect Reason Code. + // If the Remaining Length is less than 1 the value of 0x00 (Normal disconnection) is used. + if (body.Count == 0) + { + return new MqttDisconnectPacket { - var willPropertiesReader = new MqttV5PropertiesReader(_bufferReader); + ReasonCode = MqttDisconnectReasonCode.NormalDisconnection + }; + } - while (willPropertiesReader.MoveNext()) - { - if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.PayloadFormatIndicator) - { - packet.WillPayloadFormatIndicator = willPropertiesReader.ReadPayloadFormatIndicator(); - } - else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.MessageExpiryInterval) - { - packet.WillMessageExpiryInterval = willPropertiesReader.ReadMessageExpiryInterval(); - } - else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.ResponseTopic) - { - packet.WillResponseTopic = willPropertiesReader.ReadResponseTopic(); - } - else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.CorrelationData) - { - packet.WillCorrelationData = willPropertiesReader.ReadCorrelationData(); - } - else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.ContentType) - { - packet.WillContentType = willPropertiesReader.ReadContentType(); - } - else if (willPropertiesReader.CurrentPropertyId == MqttPropertyId.WillDelayInterval) - { - packet.WillDelayInterval = willPropertiesReader.ReadWillDelayInterval(); - } - else - { - willPropertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPublishPacket)); - } - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - packet.WillTopic = _bufferReader.ReadString(); - packet.WillMessage = _bufferReader.ReadBinaryData(); - packet.WillUserProperties = willPropertiesReader.CollectedUserProperties; - } + var packet = new MqttDisconnectPacket + { + ReasonCode = (MqttDisconnectReasonCode)_bufferReader.ReadByte() + }; - if (usernameFlag) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) { - packet.Username = _bufferReader.ReadString(); + packet.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); } - - if (passwordFlag) + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - packet.Password = _bufferReader.ReadBinaryData(); + packet.ReasonString = propertiesReader.ReadReasonString(); } - - return packet; - } - - MqttPacket DecodeDisconnectPacket(ArraySegment body) - { - // From RFC: 3.14.2.1 Disconnect Reason Code - // Byte 1 in the Variable Header is the Disconnect Reason Code. - // If the Remaining Length is less than 1 the value of 0x00 (Normal disconnection) is used. - if (body.Count == 0) + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerReference) { - return new MqttDisconnectPacket - { - ReasonCode = MqttDisconnectReasonCode.NormalDisconnection - }; + packet.ServerReference = propertiesReader.ReadServerReference(); } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttDisconnectPacket)); + } + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + packet.UserProperties = propertiesReader.CollectedUserProperties; - var packet = new MqttDisconnectPacket - { - ReasonCode = (MqttDisconnectReasonCode)_bufferReader.ReadByte() - }; + return packet; + } - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) - { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) - { - packet.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ServerReference) - { - packet.ServerReference = propertiesReader.ReadServerReference(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttDisconnectPacket)); - } - } + MqttPubAckPacket DecodePubAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - packet.UserProperties = propertiesReader.CollectedUserProperties; + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - return packet; - } + var packet = new MqttPubAckPacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - MqttPacket DecodePubAckPacket(ArraySegment body) + if (_bufferReader.EndOfStream) { - ThrowIfBodyIsEmpty(body); + packet.ReasonCode = MqttPubAckReasonCode.Success; + return packet; + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + packet.ReasonCode = (MqttPubAckReasonCode)_bufferReader.ReadByte(); - var packet = new MqttPubAckPacket + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; - - if (_bufferReader.EndOfStream) + packet.ReasonString = propertiesReader.ReadReasonString(); + } + else { - packet.ReasonCode = MqttPubAckReasonCode.Success; - return packet; + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubAckPacket)); } + } - packet.ReasonCode = (MqttPubAckReasonCode)_bufferReader.ReadByte(); + packet.UserProperties = propertiesReader.CollectedUserProperties; - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) - { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubAckPacket)); - } - } + return packet; + } - packet.UserProperties = propertiesReader.CollectedUserProperties; + MqttPubCompPacket DecodePubCompPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - return packet; - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodePubCompPacket(ArraySegment body) + var packet = new MqttPubCompPacket { - ThrowIfBodyIsEmpty(body); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + if (_bufferReader.EndOfStream) + { + packet.ReasonCode = MqttPubCompReasonCode.Success; + return packet; + } - var packet = new MqttPubCompPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + packet.ReasonCode = (MqttPubCompReasonCode)_bufferReader.ReadByte(); - if (_bufferReader.EndOfStream) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - packet.ReasonCode = MqttPubCompReasonCode.Success; - return packet; + packet.ReasonString = propertiesReader.ReadReasonString(); } - - packet.ReasonCode = (MqttPubCompReasonCode)_bufferReader.ReadByte(); - - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + else { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubCompPacket)); - } + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubCompPacket)); } + } - packet.UserProperties = propertiesReader.CollectedUserProperties; + packet.UserProperties = propertiesReader.CollectedUserProperties; - return packet; - } + return packet; + } - MqttPacket DecodePublishPacket(byte header, ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + MqttPublishPacket DecodePublishPacket(byte header, ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - var retain = (header & 1) > 0; - var qos = (MqttQualityOfServiceLevel)((header >> 1) & 3); - var dup = ((header >> 3) & 1) > 0; + var retain = (header & 1) > 0; + var qos = (MqttQualityOfServiceLevel)((header >> 1) & 3); + var dup = ((header >> 3) & 1) > 0; - var packet = new MqttPublishPacket - { - Topic = _bufferReader.ReadString(), - Retain = retain, - QualityOfServiceLevel = qos, - Dup = dup - }; + var packet = new MqttPublishPacket + { + Topic = _bufferReader.ReadString(), + Retain = retain, + QualityOfServiceLevel = qos, + Dup = dup + }; + + if (qos > 0) + { + packet.PacketIdentifier = _bufferReader.ReadTwoByteInteger(); + } - if (qos > 0) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.PayloadFormatIndicator) { - packet.PacketIdentifier = _bufferReader.ReadTwoByteInteger(); + packet.PayloadFormatIndicator = propertiesReader.ReadPayloadFormatIndicator(); } - - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MessageExpiryInterval) { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.PayloadFormatIndicator) - { - packet.PayloadFormatIndicator = propertiesReader.ReadPayloadFormatIndicator(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.MessageExpiryInterval) - { - packet.MessageExpiryInterval = propertiesReader.ReadMessageExpiryInterval(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAlias) - { - packet.TopicAlias = propertiesReader.ReadTopicAlias(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ResponseTopic) - { - packet.ResponseTopic = propertiesReader.ReadResponseTopic(); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.CorrelationData) + packet.MessageExpiryInterval = propertiesReader.ReadMessageExpiryInterval(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.TopicAlias) + { + packet.TopicAlias = propertiesReader.ReadTopicAlias(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ResponseTopic) + { + packet.ResponseTopic = propertiesReader.ReadResponseTopic(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.CorrelationData) + { + packet.CorrelationData = propertiesReader.ReadCorrelationData(); + } + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) + { + if (packet.SubscriptionIdentifiers == null) { - packet.CorrelationData = propertiesReader.ReadCorrelationData(); + packet.SubscriptionIdentifiers = new List(); } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) - { - if (packet.SubscriptionIdentifiers == null) - { - packet.SubscriptionIdentifiers = new List(); - } - packet.SubscriptionIdentifiers.Add(propertiesReader.ReadSubscriptionIdentifier()); - } - else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ContentType) - { - packet.ContentType = propertiesReader.ReadContentType(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPublishPacket)); - } + packet.SubscriptionIdentifiers.Add(propertiesReader.ReadSubscriptionIdentifier()); } - - packet.UserProperties = propertiesReader.CollectedUserProperties; - - if (!_bufferReader.EndOfStream) + else if (propertiesReader.CurrentPropertyId == MqttPropertyId.ContentType) { - packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); + packet.ContentType = propertiesReader.ReadContentType(); } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPublishPacket)); + } + } - return packet; + packet.UserProperties = propertiesReader.CollectedUserProperties; + + if (!_bufferReader.EndOfStream) + { + packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); } - MqttPacket DecodePubRecPacket(ArraySegment body) + return packet; + } + + MqttPubRecPacket DecodePubRecPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); + + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + + var packet = new MqttPubRecPacket { - ThrowIfBodyIsEmpty(body); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + if (_bufferReader.EndOfStream) + { + packet.ReasonCode = MqttPubRecReasonCode.Success; + return packet; + } - var packet = new MqttPubRecPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + packet.ReasonCode = (MqttPubRecReasonCode)_bufferReader.ReadByte(); - if (_bufferReader.EndOfStream) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - packet.ReasonCode = MqttPubRecReasonCode.Success; - return packet; + packet.ReasonString = propertiesReader.ReadReasonString(); } - - packet.ReasonCode = (MqttPubRecReasonCode)_bufferReader.ReadByte(); - - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + else { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubRecPacket)); - } + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubRecPacket)); } + } - packet.UserProperties = propertiesReader.CollectedUserProperties; + packet.UserProperties = propertiesReader.CollectedUserProperties; - return packet; - } + return packet; + } + + MqttPubRelPacket DecodePubRelPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); + + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodePubRelPacket(ArraySegment body) + var packet = new MqttPubRelPacket { - ThrowIfBodyIsEmpty(body); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + if (_bufferReader.EndOfStream) + { + packet.ReasonCode = MqttPubRelReasonCode.Success; + return packet; + } - var packet = new MqttPubRelPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + packet.ReasonCode = (MqttPubRelReasonCode)_bufferReader.ReadByte(); - if (_bufferReader.EndOfStream) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - packet.ReasonCode = MqttPubRelReasonCode.Success; - return packet; + packet.ReasonString = propertiesReader.ReadReasonString(); } - - packet.ReasonCode = (MqttPubRelReasonCode)_bufferReader.ReadByte(); - - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + else { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubRelPacket)); - } + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttPubRelPacket)); } + } - packet.UserProperties = propertiesReader.CollectedUserProperties; + packet.UserProperties = propertiesReader.CollectedUserProperties; - return packet; - } + return packet; + } - MqttPacket DecodeSubAckPacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + MqttSubAckPacket DecodeSubAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - var packet = new MqttSubAckPacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + var packet = new MqttSubAckPacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttSubAckPacket)); - } + packet.ReasonString = propertiesReader.ReadReasonString(); } - - packet.UserProperties = propertiesReader.CollectedUserProperties; - - packet.ReasonCodes = new List(_bufferReader.BytesLeft); - while (!_bufferReader.EndOfStream) + else { - var reasonCode = (MqttSubscribeReasonCode)_bufferReader.ReadByte(); - packet.ReasonCodes.Add(reasonCode); + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttSubAckPacket)); } - - return packet; } - MqttPacket DecodeSubscribePacket(ArraySegment body) + packet.UserProperties = propertiesReader.CollectedUserProperties; + + packet.ReasonCodes = new List(_bufferReader.BytesLeft); + while (!_bufferReader.EndOfStream) { - ThrowIfBodyIsEmpty(body); + var reasonCode = (MqttSubscribeReasonCode)_bufferReader.ReadByte(); + packet.ReasonCodes.Add(reasonCode); + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + return packet; + } - var packet = new MqttSubscribePacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + MqttSubscribePacket DecodeSubscribePacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); + + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + var packet = new MqttSubscribePacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; + + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.SubscriptionIdentifier) - { - packet.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttSubscribePacket)); - } + packet.SubscriptionIdentifier = propertiesReader.ReadSubscriptionIdentifier(); + } + else + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttSubscribePacket)); } + } - packet.UserProperties = propertiesReader.CollectedUserProperties; + packet.UserProperties = propertiesReader.CollectedUserProperties; - while (!_bufferReader.EndOfStream) - { - var topic = _bufferReader.ReadString(); - var options = _bufferReader.ReadByte(); + while (!_bufferReader.EndOfStream) + { + var topic = _bufferReader.ReadString(); + var options = _bufferReader.ReadByte(); + + var qos = (MqttQualityOfServiceLevel)(options & 3); + var noLocal = (options & (1 << 2)) > 0; + var retainAsPublished = (options & (1 << 3)) > 0; + var retainHandling = (MqttRetainHandling)((options >> 4) & 3); + + packet.TopicFilters.Add( + new MqttTopicFilter + { + Topic = topic, + QualityOfServiceLevel = qos, + NoLocal = noLocal, + RetainAsPublished = retainAsPublished, + RetainHandling = retainHandling + }); + } - var qos = (MqttQualityOfServiceLevel)(options & 3); - var noLocal = (options & (1 << 2)) > 0; - var retainAsPublished = (options & (1 << 3)) > 0; - var retainHandling = (MqttRetainHandling)((options >> 4) & 3); + return packet; + } - packet.TopicFilters.Add( - new MqttTopicFilter - { - Topic = topic, - QualityOfServiceLevel = qos, - NoLocal = noLocal, - RetainAsPublished = retainAsPublished, - RetainHandling = retainHandling - }); - } + MqttUnsubAckPacket DecodeUnsubAckPacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - return packet; - } + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - MqttPacket DecodeUnsubAckPacket(ArraySegment body) + var packet = new MqttUnsubAckPacket { - ThrowIfBodyIsEmpty(body); - - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - var packet = new MqttUnsubAckPacket + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; - - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) + packet.ReasonString = propertiesReader.ReadReasonString(); + } + else { - if (propertiesReader.CurrentPropertyId == MqttPropertyId.ReasonString) - { - packet.ReasonString = propertiesReader.ReadReasonString(); - } - else - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttUnsubAckPacket)); - } + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttUnsubAckPacket)); } + } - packet.UserProperties = propertiesReader.CollectedUserProperties; - - packet.ReasonCodes = new List(_bufferReader.BytesLeft); + packet.UserProperties = propertiesReader.CollectedUserProperties; - while (!_bufferReader.EndOfStream) - { - var reasonCode = (MqttUnsubscribeReasonCode)_bufferReader.ReadByte(); - packet.ReasonCodes.Add(reasonCode); - } + packet.ReasonCodes = new List(_bufferReader.BytesLeft); - return packet; + while (!_bufferReader.EndOfStream) + { + var reasonCode = (MqttUnsubscribeReasonCode)_bufferReader.ReadByte(); + packet.ReasonCodes.Add(reasonCode); } - MqttPacket DecodeUnsubscribePacket(ArraySegment body) - { - ThrowIfBodyIsEmpty(body); + return packet; + } - _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); + MqttUnsubscribePacket DecodeUnsubscribePacket(ArraySegment body) + { + ThrowIfBodyIsEmpty(body); - var packet = new MqttUnsubscribePacket - { - PacketIdentifier = _bufferReader.ReadTwoByteInteger() - }; + _bufferReader.SetBuffer(body.Array, body.Offset, body.Count); - var propertiesReader = new MqttV5PropertiesReader(_bufferReader); - while (propertiesReader.MoveNext()) - { - propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttUnsubscribePacket)); - } + var packet = new MqttUnsubscribePacket + { + PacketIdentifier = _bufferReader.ReadTwoByteInteger() + }; - packet.UserProperties = propertiesReader.CollectedUserProperties; + var propertiesReader = new MqttV5PropertiesReader(_bufferReader); + while (propertiesReader.MoveNext()) + { + propertiesReader.ThrowInvalidPropertyIdException(typeof(MqttUnsubscribePacket)); + } - while (!_bufferReader.EndOfStream) - { - packet.TopicFilters.Add(_bufferReader.ReadString()); - } + packet.UserProperties = propertiesReader.CollectedUserProperties; - return packet; + while (!_bufferReader.EndOfStream) + { + packet.TopicFilters.Add(_bufferReader.ReadString()); } - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - static void ThrowIfBodyIsEmpty(ArraySegment body) + return packet; + } + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + static void ThrowIfBodyIsEmpty(ArraySegment body) + { + if (body.Count == 0) { - if (body.Count == 0) - { - throw new MqttProtocolViolationException("Data from the body is required but not present."); - } + throw new MqttProtocolViolationException("Data from the body is required but not present."); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs index 9d5a6d09b..9c4e90137 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; using System.Linq; using MQTTnet.Exceptions; using MQTTnet.Packets; @@ -11,34 +10,29 @@ namespace MQTTnet.Formatter.V5; -public sealed class MqttV5PacketEncoder +public sealed class MqttV5PacketEncoder(MqttBufferWriter bufferWriter) { const int FixedHeaderSize = 1; - readonly MqttBufferWriter _bufferWriter; + readonly MqttBufferWriter _bufferWriter = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); readonly MqttV5PropertiesWriter _propertiesWriter = new(new MqttBufferWriter(1024, 4096)); - public MqttV5PacketEncoder(MqttBufferWriter bufferWriter) - { - _bufferWriter = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); - } - public MqttPacketBuffer Encode(MqttPacket packet) { ArgumentNullException.ThrowIfNull(packet); - // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) - const int ReservedHeaderSize = 5; - _bufferWriter.Reset(ReservedHeaderSize); - _bufferWriter.Seek(ReservedHeaderSize); + // Leave enough headspace for max header size (fixed + 4 variable remaining length = 5 bytes) + const int reservedHeaderSize = 5; + + _bufferWriter.Reset(reservedHeaderSize); + _bufferWriter.Seek(reservedHeaderSize); var fixedHeader = EncodePacket(packet); - var remainingLength = (uint)_bufferWriter.Length - ReservedHeaderSize; + var remainingLength = (uint)_bufferWriter.Length - reservedHeaderSize; - ReadOnlySequence payload = default; if (packet is MqttPublishPacket publishPacket) { - payload = publishPacket.Payload; + var payload = publishPacket.Payload; remainingLength += (uint)payload.Length; } else @@ -226,42 +220,25 @@ byte EncodeDisconnectPacket(MqttDisconnectPacket packet) byte EncodePacket(MqttPacket packet) { - switch (packet) - { - case MqttConnectPacket connectPacket: - return EncodeConnectPacket(connectPacket); - case MqttConnAckPacket connAckPacket: - return EncodeConnAckPacket(connAckPacket); - case MqttDisconnectPacket disconnectPacket: - return EncodeDisconnectPacket(disconnectPacket); - case MqttPingReqPacket _: - return EncodePingReqPacket(); - case MqttPingRespPacket _: - return EncodePingRespPacket(); - case MqttPublishPacket publishPacket: - return EncodePublishPacket(publishPacket); - case MqttPubAckPacket pubAckPacket: - return EncodePubAckPacket(pubAckPacket); - case MqttPubRecPacket pubRecPacket: - return EncodePubRecPacket(pubRecPacket); - case MqttPubRelPacket pubRelPacket: - return EncodePubRelPacket(pubRelPacket); - case MqttPubCompPacket pubCompPacket: - return EncodePubCompPacket(pubCompPacket); - case MqttSubscribePacket subscribePacket: - return EncodeSubscribePacket(subscribePacket); - case MqttSubAckPacket subAckPacket: - return EncodeSubAckPacket(subAckPacket); - case MqttUnsubscribePacket unsubscribePacket: - return EncodeUnsubscribePacket(unsubscribePacket); - case MqttUnsubAckPacket unsubAckPacket: - return EncodeUnsubAckPacket(unsubAckPacket); - case MqttAuthPacket authPacket: - return EncodeAuthPacket(authPacket); - - default: - throw new MqttProtocolViolationException("Packet type invalid."); - } + return packet switch + { + MqttConnectPacket connectPacket => EncodeConnectPacket(connectPacket), + MqttConnAckPacket connAckPacket => EncodeConnAckPacket(connAckPacket), + MqttDisconnectPacket disconnectPacket => EncodeDisconnectPacket(disconnectPacket), + MqttPingReqPacket _ => EncodePingReqPacket(), + MqttPingRespPacket _ => EncodePingRespPacket(), + MqttPublishPacket publishPacket => EncodePublishPacket(publishPacket), + MqttPubAckPacket pubAckPacket => EncodePubAckPacket(pubAckPacket), + MqttPubRecPacket pubRecPacket => EncodePubRecPacket(pubRecPacket), + MqttPubRelPacket pubRelPacket => EncodePubRelPacket(pubRelPacket), + MqttPubCompPacket pubCompPacket => EncodePubCompPacket(pubCompPacket), + MqttSubscribePacket subscribePacket => EncodeSubscribePacket(subscribePacket), + MqttSubAckPacket subAckPacket => EncodeSubAckPacket(subAckPacket), + MqttUnsubscribePacket unsubscribePacket => EncodeUnsubscribePacket(unsubscribePacket), + MqttUnsubAckPacket unsubAckPacket => EncodeUnsubAckPacket(unsubAckPacket), + MqttAuthPacket authPacket => EncodeAuthPacket(authPacket), + _ => throw new MqttProtocolViolationException("Packet type invalid.") + }; } static byte EncodePingReqPacket() @@ -413,7 +390,7 @@ byte EncodePubRelPacket(MqttPubRelPacket packet) byte EncodeSubAckPacket(MqttSubAckPacket packet) { - if (packet.ReasonCodes?.Any() != true) + if (packet.ReasonCodes?.Count == 0) { throw new MqttProtocolViolationException("At least one reason code must be set[MQTT - 3.8.3 - 3]."); } @@ -438,7 +415,7 @@ byte EncodeSubAckPacket(MqttSubAckPacket packet) byte EncodeSubscribePacket(MqttSubscribePacket packet) { - if (packet.TopicFilters?.Any() != true) + if (packet.TopicFilters?.Count == 0) { throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); } @@ -509,7 +486,7 @@ byte EncodeUnsubAckPacket(MqttUnsubAckPacket packet) byte EncodeUnsubscribePacket(MqttUnsubscribePacket packet) { - if (packet.TopicFilters?.Any() != true) + if (packet.TopicFilters?.Count == 0) { throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2]."); } diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs index 3cf4776cf..ec6a12298 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs @@ -5,27 +5,26 @@ using MQTTnet.Adapter; using MQTTnet.Packets; -namespace MQTTnet.Formatter.V5 +namespace MQTTnet.Formatter.V5; + +public sealed class MqttV5PacketFormatter : IMqttPacketFormatter { - public sealed class MqttV5PacketFormatter : IMqttPacketFormatter - { - readonly MqttV5PacketDecoder _decoder; - readonly MqttV5PacketEncoder _encoder; + readonly MqttV5PacketDecoder _decoder; + readonly MqttV5PacketEncoder _encoder; - public MqttV5PacketFormatter(MqttBufferWriter bufferWriter) - { - _decoder = new MqttV5PacketDecoder(); - _encoder = new MqttV5PacketEncoder(bufferWriter); - } + public MqttV5PacketFormatter(MqttBufferWriter bufferWriter) + { + _decoder = new MqttV5PacketDecoder(); + _encoder = new MqttV5PacketEncoder(bufferWriter); + } - public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) - { - return _decoder.Decode(receivedMqttPacket); - } + public MqttPacket Decode(ReceivedMqttPacket receivedPacket) + { + return _decoder.Decode(receivedPacket); + } - public MqttPacketBuffer Encode(MqttPacket mqttPacket) - { - return _encoder.Encode(mqttPacket); - } + public MqttPacketBuffer Encode(MqttPacket packet) + { + return _encoder.Encode(packet); } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs index e5a3efe7e..849830886 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs @@ -8,213 +8,212 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter.V5 +namespace MQTTnet.Formatter.V5; + +public struct MqttV5PropertiesReader { - public struct MqttV5PropertiesReader + readonly MqttBufferReader _body; + readonly int _length; + readonly int _targetOffset; + + public MqttV5PropertiesReader(MqttBufferReader body) { - readonly MqttBufferReader _body; - readonly int _length; - readonly int _targetOffset; + _body = body ?? throw new ArgumentNullException(nameof(body)); - public MqttV5PropertiesReader(MqttBufferReader body) + if (!body.EndOfStream) { - _body = body ?? throw new ArgumentNullException(nameof(body)); - - if (!body.EndOfStream) - { - _length = (int)body.ReadVariableByteInteger(); - } - else - { - _length = 0; - } + _length = (int)body.ReadVariableByteInteger(); + } + else + { + _length = 0; + } - _targetOffset = body.Position + _length; + _targetOffset = body.Position + _length; - CollectedUserProperties = null; - CurrentPropertyId = MqttPropertyId.None; - } + CollectedUserProperties = null; + CurrentPropertyId = MqttPropertyId.None; + } - public List CollectedUserProperties { get; private set; } + public List CollectedUserProperties { get; private set; } - public MqttPropertyId CurrentPropertyId { get; private set; } + public MqttPropertyId CurrentPropertyId { get; private set; } - public bool MoveNext() + public bool MoveNext() + { + while (true) { - while (true) + if (_length == 0) { - if (_length == 0) - { - return false; - } - - if (_body.Position >= _targetOffset) - { - return false; - } + return false; + } - CurrentPropertyId = (MqttPropertyId)_body.ReadByte(); + if (_body.Position >= _targetOffset) + { + return false; + } - // User properties are special because they can appear multiple times in the - // buffer and at any position. So we collect them here to expose them as a - // final result list. - if (CurrentPropertyId == MqttPropertyId.UserProperty) - { - var name = _body.ReadString(); - var value = _body.ReadString(); + CurrentPropertyId = (MqttPropertyId)_body.ReadByte(); - if (CollectedUserProperties == null) - { - CollectedUserProperties = new List(); - } + // User properties are special because they can appear multiple times in the + // buffer and at any position. So we collect them here to expose them as a + // final result list. + if (CurrentPropertyId == MqttPropertyId.UserProperty) + { + var name = _body.ReadString(); + var value = _body.ReadString(); - CollectedUserProperties.Add(new MqttUserProperty(name, value)); - continue; + if (CollectedUserProperties == null) + { + CollectedUserProperties = new List(); } - return true; + CollectedUserProperties.Add(new MqttUserProperty(name, value)); + continue; } - } - public string ReadAssignedClientIdentifier() - { - return _body.ReadString(); - } - - public byte[] ReadAuthenticationData() - { - return _body.ReadBinaryData(); + return true; } + } - public string ReadAuthenticationMethod() - { - return _body.ReadString(); - } + public string ReadAssignedClientIdentifier() + { + return _body.ReadString(); + } - public string ReadContentType() - { - return _body.ReadString(); - } + public byte[] ReadAuthenticationData() + { + return _body.ReadBinaryData(); + } - public byte[] ReadCorrelationData() - { - return _body.ReadBinaryData(); - } + public string ReadAuthenticationMethod() + { + return _body.ReadString(); + } - public uint ReadMaximumPacketSize() - { - return _body.ReadFourByteInteger(); - } + public string ReadContentType() + { + return _body.ReadString(); + } - public MqttQualityOfServiceLevel ReadMaximumQoS() - { - var value = _body.ReadByte(); - if (value > 1) - { - throw new MqttProtocolViolationException($"Unexpected Maximum QoS value: {value}"); - } + public byte[] ReadCorrelationData() + { + return _body.ReadBinaryData(); + } - return (MqttQualityOfServiceLevel)value; - } + public uint ReadMaximumPacketSize() + { + return _body.ReadFourByteInteger(); + } - public uint ReadMessageExpiryInterval() + public MqttQualityOfServiceLevel ReadMaximumQoS() + { + var value = _body.ReadByte(); + if (value > 1) { - return _body.ReadFourByteInteger(); + throw new MqttProtocolViolationException($"Unexpected Maximum QoS value: {value}"); } - public MqttPayloadFormatIndicator ReadPayloadFormatIndicator() - { - return (MqttPayloadFormatIndicator)_body.ReadByte(); - } + return (MqttQualityOfServiceLevel)value; + } - public string ReadReasonString() - { - return _body.ReadString(); - } + public uint ReadMessageExpiryInterval() + { + return _body.ReadFourByteInteger(); + } - public ushort ReadReceiveMaximum() - { - return _body.ReadTwoByteInteger(); - } + public MqttPayloadFormatIndicator ReadPayloadFormatIndicator() + { + return (MqttPayloadFormatIndicator)_body.ReadByte(); + } - public string ReadResponseInformation() - { - return _body.ReadString(); - } + public string ReadReasonString() + { + return _body.ReadString(); + } - public string ReadResponseTopic() - { - return _body.ReadString(); - } + public ushort ReadReceiveMaximum() + { + return _body.ReadTwoByteInteger(); + } - public bool ReadRetainAvailable() - { - return _body.ReadByte() == 1; - } + public string ReadResponseInformation() + { + return _body.ReadString(); + } - public ushort ReadServerKeepAlive() - { - return _body.ReadTwoByteInteger(); - } + public string ReadResponseTopic() + { + return _body.ReadString(); + } - public string ReadServerReference() - { - return _body.ReadString(); - } + public bool ReadRetainAvailable() + { + return _body.ReadByte() == 1; + } - public uint ReadSessionExpiryInterval() - { - return _body.ReadFourByteInteger(); - } + public ushort ReadServerKeepAlive() + { + return _body.ReadTwoByteInteger(); + } - public bool ReadSharedSubscriptionAvailable() - { - return _body.ReadByte() == 1; - } + public string ReadServerReference() + { + return _body.ReadString(); + } - public uint ReadSubscriptionIdentifier() - { - return _body.ReadVariableByteInteger(); - } + public uint ReadSessionExpiryInterval() + { + return _body.ReadFourByteInteger(); + } - public bool ReadSubscriptionIdentifiersAvailable() - { - return _body.ReadByte() == 1; - } + public bool ReadSharedSubscriptionAvailable() + { + return _body.ReadByte() == 1; + } - public ushort ReadTopicAlias() - { - return _body.ReadTwoByteInteger(); - } + public uint ReadSubscriptionIdentifier() + { + return _body.ReadVariableByteInteger(); + } - public ushort ReadTopicAliasMaximum() - { - return _body.ReadTwoByteInteger(); - } + public bool ReadSubscriptionIdentifiersAvailable() + { + return _body.ReadByte() == 1; + } - public bool ReadWildcardSubscriptionAvailable() - { - return _body.ReadByte() == 1; - } + public ushort ReadTopicAlias() + { + return _body.ReadTwoByteInteger(); + } - public uint ReadWillDelayInterval() - { - return _body.ReadFourByteInteger(); - } + public ushort ReadTopicAliasMaximum() + { + return _body.ReadTwoByteInteger(); + } - public bool RequestProblemInformation() - { - return _body.ReadByte() == 1; - } + public bool ReadWildcardSubscriptionAvailable() + { + return _body.ReadByte() == 1; + } - public bool RequestResponseInformation() - { - return _body.ReadByte() == 1; - } + public uint ReadWillDelayInterval() + { + return _body.ReadFourByteInteger(); + } - public void ThrowInvalidPropertyIdException(Type type) - { - throw new MqttProtocolViolationException($"Property ID '{CurrentPropertyId}' is not supported for package type '{type.Name}'."); - } + public bool RequestProblemInformation() + { + return _body.ReadByte() == 1; + } + + public bool RequestResponseInformation() + { + return _body.ReadByte() == 1; + } + + public void ThrowInvalidPropertyIdException(Type type) + { + throw new MqttProtocolViolationException($"Property ID '{CurrentPropertyId}' is not supported for package type '{type.Name}'."); } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs index 25546d332..5d1387187 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs @@ -7,347 +7,339 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter.V5 +namespace MQTTnet.Formatter.V5; + +public sealed class MqttV5PropertiesWriter { - public sealed class MqttV5PropertiesWriter + readonly MqttBufferWriter _bufferWriter; + + public MqttV5PropertiesWriter(MqttBufferWriter bufferWriter) { - readonly MqttBufferWriter _bufferWriter; + _bufferWriter = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); + } - public MqttV5PropertiesWriter(MqttBufferWriter bufferWriter) - { - _bufferWriter = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); - } + public int Length => _bufferWriter.Length; - public int Length => _bufferWriter.Length; + public void Reset() + { + _bufferWriter.Reset(0); + _bufferWriter.Cleanup(); + } - public void Reset() - { - _bufferWriter.Reset(0); - _bufferWriter.Cleanup(); - } + public void WriteAssignedClientIdentifier(string value) + { + Write(MqttPropertyId.AssignedClientIdentifier, value); + } - public void WriteAssignedClientIdentifier(string value) - { - Write(MqttPropertyId.AssignedClientIdentifier, value); - } + public void WriteAuthenticationData(byte[] value) + { + Write(MqttPropertyId.AuthenticationData, value); + } - public void WriteAuthenticationData(byte[] value) - { - Write(MqttPropertyId.AuthenticationData, value); - } + public void WriteAuthenticationMethod(string value) + { + Write(MqttPropertyId.AuthenticationMethod, value); + } - public void WriteAuthenticationMethod(string value) - { - Write(MqttPropertyId.AuthenticationMethod, value); - } + public void WriteContentType(string value) + { + Write(MqttPropertyId.ContentType, value); + } - public void WriteContentType(string value) - { - Write(MqttPropertyId.ContentType, value); - } + public void WriteCorrelationData(byte[] value) + { + Write(MqttPropertyId.CorrelationData, value); + } - public void WriteCorrelationData(byte[] value) + public void WriteMaximumPacketSize(uint value) + { + // It is a Protocol Error to include the Maximum Packet Size more than once, or for the value to be set to zero. + if (value == 0) { - Write(MqttPropertyId.CorrelationData, value); + return; } - public void WriteMaximumPacketSize(uint value) - { - // It is a Protocol Error to include the Maximum Packet Size more than once, or for the value to be set to zero. - if (value == 0) - { - return; - } + WriteAsFourByteInteger(MqttPropertyId.MaximumPacketSize, value); + } - WriteAsFourByteInteger(MqttPropertyId.MaximumPacketSize, value); + public void WriteMaximumQoS(MqttQualityOfServiceLevel value) + { + // It is a Protocol Error to include Maximum QoS more than once, or to have a value other than 0 or 1. If the Maximum QoS is absent, the Client uses a Maximum QoS of 2. + if (value == MqttQualityOfServiceLevel.ExactlyOnce) + { + return; } - public void WriteMaximumQoS(MqttQualityOfServiceLevel value) + Write(MqttPropertyId.MaximumQoS, value == MqttQualityOfServiceLevel.AtLeastOnce ? (byte)0x1 : (byte)0x0); + } + + public void WriteMessageExpiryInterval(uint value) + { + // If absent, the Application Message does not expire. + // This library uses 0 to indicate no expiration. + if (value == 0) { - // It is a Protocol Error to include Maximum QoS more than once, or to have a value other than 0 or 1. If the Maximum QoS is absent, the Client uses a Maximum QoS of 2. - if (value == MqttQualityOfServiceLevel.ExactlyOnce) - { - return; - } - - if (value == MqttQualityOfServiceLevel.AtLeastOnce) - { - Write(MqttPropertyId.MaximumQoS, 0x1); - } - else - { - Write(MqttPropertyId.MaximumQoS, 0x0); - } + return; } - public void WriteMessageExpiryInterval(uint value) + WriteAsFourByteInteger(MqttPropertyId.MessageExpiryInterval, value); + } + + public void WritePayloadFormatIndicator(MqttPayloadFormatIndicator value) + { + // 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a Payload Format Indicator. + if (value == MqttPayloadFormatIndicator.Unspecified) { - // If absent, the Application Message does not expire. - // This library uses 0 to indicate no expiration. - if (value == 0) - { - return; - } - - WriteAsFourByteInteger(MqttPropertyId.MessageExpiryInterval, value); + return; } - public void WritePayloadFormatIndicator(MqttPayloadFormatIndicator value) - { - // 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a Payload Format Indicator. - if (value == MqttPayloadFormatIndicator.Unspecified) - { - return; - } + Write(MqttPropertyId.PayloadFormatIndicator, (byte)value); + } - Write(MqttPropertyId.PayloadFormatIndicator, (byte)value); - } + public void WriteReasonString(string value) + { + Write(MqttPropertyId.ReasonString, value); + } - public void WriteReasonString(string value) + public void WriteReceiveMaximum(ushort value) + { + // It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. + if (value == 0) { - Write(MqttPropertyId.ReasonString, value); + return; } - public void WriteReceiveMaximum(ushort value) - { - // It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. - if (value == 0) - { - return; - } + Write(MqttPropertyId.ReceiveMaximum, value); + } - Write(MqttPropertyId.ReceiveMaximum, value); + public void WriteRequestProblemInformation(bool value) + { + // If the Request Problem Information is absent, the value of 1 is used. + if (value) + { + return; } - public void WriteRequestProblemInformation(bool value) - { - // If the Request Problem Information is absent, the value of 1 is used. - if (value) - { - return; - } + Write(MqttPropertyId.RequestProblemInformation, false); + } - Write(MqttPropertyId.RequestProblemInformation, false); + public void WriteRequestResponseInformation(bool value) + { + // If the Request Response Information is absent, the value of 0 is used. + if (!value) + { + return; } - public void WriteRequestResponseInformation(bool value) - { - // If the Request Response Information is absent, the value of 0 is used. - if (!value) - { - return; - } + Write(MqttPropertyId.RequestResponseInformation, true); + } - Write(MqttPropertyId.RequestResponseInformation, true); - } + public void WriteResponseInformation(string value) + { + Write(MqttPropertyId.ResponseInformation, value); + } - public void WriteResponseInformation(string value) - { - Write(MqttPropertyId.ResponseInformation, value); - } + public void WriteResponseTopic(string value) + { + Write(MqttPropertyId.ResponseTopic, value); + } - public void WriteResponseTopic(string value) + public void WriteRetainAvailable(bool value) + { + if (value) { - Write(MqttPropertyId.ResponseTopic, value); + // Absence of the flag means it is supported! + return; } - public void WriteRetainAvailable(bool value) - { - if (value) - { - // Absence of the flag means it is supported! - return; - } + Write(MqttPropertyId.RetainAvailable, false); + } - Write(MqttPropertyId.RetainAvailable, false); + public void WriteServerKeepAlive(ushort value) + { + if (value == 0) + { + return; } - public void WriteServerKeepAlive(ushort value) - { - if (value == 0) - { - return; - } + Write(MqttPropertyId.ServerKeepAlive, value); + } - Write(MqttPropertyId.ServerKeepAlive, value); - } + public void WriteServerReference(string value) + { + Write(MqttPropertyId.ServerReference, value); + } - public void WriteServerReference(string value) + public void WriteSessionExpiryInterval(uint value) + { + // If the Session Expiry Interval is absent the value 0 is used. + if (value == 0) { - Write(MqttPropertyId.ServerReference, value); + return; } - public void WriteSessionExpiryInterval(uint value) - { - // If the Session Expiry Interval is absent the value 0 is used. - if (value == 0) - { - return; - } + WriteAsFourByteInteger(MqttPropertyId.SessionExpiryInterval, value); + } - WriteAsFourByteInteger(MqttPropertyId.SessionExpiryInterval, value); + public void WriteSharedSubscriptionAvailable(bool value) + { + if (value) + { + // Absence of the flag means it is supported! + return; } - public void WriteSharedSubscriptionAvailable(bool value) - { - if (value) - { - // Absence of the flag means it is supported! - return; - } + Write(MqttPropertyId.SharedSubscriptionAvailable, false); + } - Write(MqttPropertyId.SharedSubscriptionAvailable, false); - } + public void WriteSubscriptionIdentifier(uint value) + { + WriteAsVariableByteInteger(MqttPropertyId.SubscriptionIdentifier, value); + } - public void WriteSubscriptionIdentifier(uint value) + public void WriteSubscriptionIdentifiers(ICollection value) + { + if (value == null) { - WriteAsVariableByteInteger(MqttPropertyId.SubscriptionIdentifier, value); + return; } - public void WriteSubscriptionIdentifiers(ICollection value) + foreach (var subscriptionIdentifier in value) { - if (value == null) - { - return; - } - - foreach (var subscriptionIdentifier in value) - { - WriteAsVariableByteInteger(MqttPropertyId.SubscriptionIdentifier, subscriptionIdentifier); - } + WriteAsVariableByteInteger(MqttPropertyId.SubscriptionIdentifier, subscriptionIdentifier); } + } - public void WriteSubscriptionIdentifiersAvailable(bool value) + public void WriteSubscriptionIdentifiersAvailable(bool value) + { + if (value) { - if (value) - { - // Absence of the flag means it is supported! - return; - } - - Write(MqttPropertyId.SubscriptionIdentifiersAvailable, false); + // Absence of the flag means it is supported! + return; } - public void WriteTo(MqttBufferWriter target) - { - ArgumentNullException.ThrowIfNull(target); - - target.WriteVariableByteInteger((uint)_bufferWriter.Length); - target.Write(_bufferWriter); - } + Write(MqttPropertyId.SubscriptionIdentifiersAvailable, false); + } - public void WriteTopicAlias(ushort value) - { - // A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic Alias which has the value 0. - if (value == 0) - { - return; - } + public void WriteTo(MqttBufferWriter target) + { + ArgumentNullException.ThrowIfNull(target); - Write(MqttPropertyId.TopicAlias, value); - } + target.WriteVariableByteInteger((uint)_bufferWriter.Length); + target.Write(_bufferWriter); + } - public void WriteTopicAliasMaximum(ushort value) + public void WriteTopicAlias(ushort value) + { + // A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic Alias which has the value 0. + if (value == 0) { - // If the Topic Alias Maximum property is absent, the default value is 0. - if (value == 0) - { - return; - } - - Write(MqttPropertyId.TopicAliasMaximum, value); + return; } - public void WriteUserProperties(List userProperties) - { - if (userProperties == null || userProperties.Count == 0) - { - return; - } - - foreach (var property in userProperties) - { - _bufferWriter.WriteByte((byte)MqttPropertyId.UserProperty); - _bufferWriter.WriteString(property.Name); - _bufferWriter.WriteString(property.Value); - } - } + Write(MqttPropertyId.TopicAlias, value); + } - public void WriteWildcardSubscriptionAvailable(bool value) + public void WriteTopicAliasMaximum(ushort value) + { + // If the Topic Alias Maximum property is absent, the default value is 0. + if (value == 0) { - // If not present, then Wildcard Subscriptions are supported. - if (value) - { - return; - } - - Write(MqttPropertyId.WildcardSubscriptionAvailable, false); + return; } - public void WriteWillDelayInterval(uint value) - { - // If the Will Delay Interval is absent, the default value is 0 and there is no delay before the Will Message is published. - if (value == 0) - { - return; - } + Write(MqttPropertyId.TopicAliasMaximum, value); + } - WriteAsFourByteInteger(MqttPropertyId.WillDelayInterval, value); + public void WriteUserProperties(List userProperties) + { + if (userProperties == null || userProperties.Count == 0) + { + return; } - void Write(MqttPropertyId id, bool value) + foreach (var property in userProperties) { - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteByte(value ? (byte)0x1 : (byte)0x0); + _bufferWriter.WriteByte((byte)MqttPropertyId.UserProperty); + _bufferWriter.WriteString(property.Name); + _bufferWriter.WriteString(property.Value); } + } - void Write(MqttPropertyId id, byte value) + public void WriteWildcardSubscriptionAvailable(bool value) + { + // If not present, then Wildcard Subscriptions are supported. + if (value) { - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteByte(value); + return; } - void Write(MqttPropertyId id, ushort value) + Write(MqttPropertyId.WildcardSubscriptionAvailable, false); + } + + public void WriteWillDelayInterval(uint value) + { + // If the Will Delay Interval is absent, the default value is 0 and there is no delay before the Will Message is published. + if (value == 0) { - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteTwoByteInteger(value); + return; } - void Write(MqttPropertyId id, string value) - { - if (string.IsNullOrEmpty(value)) - { - return; - } + WriteAsFourByteInteger(MqttPropertyId.WillDelayInterval, value); + } - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteString(value); - } + void Write(MqttPropertyId id, bool value) + { + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteByte(value ? (byte)0x1 : (byte)0x0); + } - void Write(MqttPropertyId id, byte[] value) - { - if (value == null) - { - return; - } + void Write(MqttPropertyId id, byte value) + { + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteByte(value); + } - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteBinary(value); - } + void Write(MqttPropertyId id, ushort value) + { + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteTwoByteInteger(value); + } - void WriteAsFourByteInteger(MqttPropertyId id, uint value) + void Write(MqttPropertyId id, string value) + { + if (string.IsNullOrEmpty(value)) { - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteByte((byte)(value >> 24)); - _bufferWriter.WriteByte((byte)(value >> 16)); - _bufferWriter.WriteByte((byte)(value >> 8)); - _bufferWriter.WriteByte((byte)value); + return; } - void WriteAsVariableByteInteger(MqttPropertyId id, uint value) + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteString(value); + } + + void Write(MqttPropertyId id, byte[] value) + { + if (value == null) { - _bufferWriter.WriteByte((byte)id); - _bufferWriter.WriteVariableByteInteger(value); + return; } + + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteBinary(value); + } + + void WriteAsFourByteInteger(MqttPropertyId id, uint value) + { + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteByte((byte)(value >> 24)); + _bufferWriter.WriteByte((byte)(value >> 16)); + _bufferWriter.WriteByte((byte)(value >> 8)); + _bufferWriter.WriteByte((byte)value); + } + + void WriteAsVariableByteInteger(MqttPropertyId id, uint value) + { + _bufferWriter.WriteByte((byte)id); + _bufferWriter.WriteVariableByteInteger(value); } } \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 0a4031f31..ef8f80ac7 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -8,43 +8,42 @@ using MQTTnet.Channel; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Implementations +namespace MQTTnet.Implementations; + +public sealed class MqttClientAdapterFactory : IMqttClientAdapterFactory { - public sealed class MqttClientAdapterFactory : IMqttClientAdapterFactory + public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger) { - public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger) - { - ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(options); - IMqttChannel channel; - switch (options.ChannelOptions) + IMqttChannel channel; + switch (options.ChannelOptions) + { + case MqttClientTcpOptions: { - case MqttClientTcpOptions _: - { - channel = new MqttTcpChannel(options); - break; - } - - case MqttClientWebSocketOptions webSocketOptions: - { - channel = new MqttWebSocketChannel(webSocketOptions); - break; - } - - default: - { - throw new NotSupportedException(); - } + channel = new MqttTcpChannel(options); + break; } - var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax); - var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter); + case MqttClientWebSocketOptions webSocketOptions: + { + channel = new MqttWebSocketChannel(webSocketOptions); + break; + } - return new MqttChannelAdapter(channel, packetFormatterAdapter, logger) + default: { - AllowPacketFragmentation = options.AllowPacketFragmentation, - PacketInspector = packetInspector - }; + throw new NotSupportedException(); + } } + + var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax); + var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter); + + return new MqttChannelAdapter(channel, packetFormatterAdapter, logger) + { + AllowPacketFragmentation = options.AllowPacketFragmentation, + PacketInspector = packetInspector + }; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 5ef20c1a6..7333261c3 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -16,313 +16,312 @@ using MQTTnet.Exceptions; using MQTTnet.Internal; -namespace MQTTnet.Implementations +namespace MQTTnet.Implementations; + +public sealed class MqttTcpChannel : IMqttChannel { - public sealed class MqttTcpChannel : IMqttChannel - { - readonly MqttClientOptions _clientOptions; - readonly MqttClientTcpOptions _tcpOptions; + readonly MqttClientOptions _clientOptions; + readonly MqttClientTcpOptions _tcpOptions; - Stream _stream; + Stream _stream; - public MqttTcpChannel() - { - } + public MqttTcpChannel() + { + } - public MqttTcpChannel(MqttClientOptions clientOptions) : this() - { - _clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); - _tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions; + public MqttTcpChannel(MqttClientOptions clientOptions) : this() + { + _clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); + _tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions; - IsSecureConnection = clientOptions.ChannelOptions?.TlsOptions?.UseTls == true; - } + IsSecureConnection = clientOptions.ChannelOptions?.TlsOptions?.UseTls == true; + } - public MqttTcpChannel(Stream stream, EndPoint remoteEndPoint, X509Certificate2 clientCertificate) : this() - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + public MqttTcpChannel(Stream stream, EndPoint remoteEndPoint, X509Certificate2 clientCertificate) : this() + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - RemoteEndPoint = remoteEndPoint; + RemoteEndPoint = remoteEndPoint; - IsSecureConnection = stream is SslStream; - ClientCertificate = clientCertificate; - } + IsSecureConnection = stream is SslStream; + ClientCertificate = clientCertificate; + } - public X509Certificate2 ClientCertificate { get; } + public X509Certificate2 ClientCertificate { get; } - public EndPoint RemoteEndPoint { get; private set; } + public EndPoint RemoteEndPoint { get; private set; } - public bool IsSecureConnection { get; } + public bool IsSecureConnection { get; } - public async Task ConnectAsync(CancellationToken cancellationToken) + public async Task ConnectAsync(CancellationToken cancellationToken) + { + CrossPlatformSocket socket = null; + try { - CrossPlatformSocket socket = null; - try + if (_tcpOptions.AddressFamily == AddressFamily.Unspecified) { - if (_tcpOptions.AddressFamily == AddressFamily.Unspecified) - { - socket = new CrossPlatformSocket(_tcpOptions.ProtocolType); - } - else - { - socket = new CrossPlatformSocket(_tcpOptions.AddressFamily, _tcpOptions.ProtocolType); - } + socket = new CrossPlatformSocket(_tcpOptions.ProtocolType); + } + else + { + socket = new CrossPlatformSocket(_tcpOptions.AddressFamily, _tcpOptions.ProtocolType); + } - if (_tcpOptions.LocalEndpoint != null) - { - socket.Bind(_tcpOptions.LocalEndpoint); - } + if (_tcpOptions.LocalEndpoint != null) + { + socket.Bind(_tcpOptions.LocalEndpoint); + } - socket.ReceiveBufferSize = _tcpOptions.BufferSize; - socket.SendBufferSize = _tcpOptions.BufferSize; - socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds; + socket.ReceiveBufferSize = _tcpOptions.BufferSize; + socket.SendBufferSize = _tcpOptions.BufferSize; + socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds; - if (_tcpOptions.ProtocolType == ProtocolType.Tcp) - { - // Other protocol types do not support the Nagle algorithm. - socket.NoDelay = _tcpOptions.NoDelay; - } + if (_tcpOptions.ProtocolType == ProtocolType.Tcp) + { + // Other protocol types do not support the Nagle algorithm. + socket.NoDelay = _tcpOptions.NoDelay; + } - if (socket.LingerState != null) - { - socket.LingerState = _tcpOptions.LingerState; - } + if (socket.LingerState != null) + { + socket.LingerState = _tcpOptions.LingerState; + } - if (_tcpOptions.DualMode.HasValue) - { - // It is important to avoid setting the flag if no specific value is set by the user - // because on IPv4 only networks the setter will always throw an exception. Regardless - // of the actual value. - socket.DualMode = _tcpOptions.DualMode.Value; - } + if (_tcpOptions.DualMode.HasValue) + { + // It is important to avoid setting the flag if no specific value is set by the user + // because on IPv4 only networks the setter will always throw an exception. Regardless + // of the actual value. + socket.DualMode = _tcpOptions.DualMode.Value; + } - await socket.ConnectAsync(_tcpOptions.RemoteEndpoint, cancellationToken).ConfigureAwait(false); + await socket.ConnectAsync(_tcpOptions.RemoteEndpoint, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - var networkStream = socket.GetStream(); + var networkStream = socket.GetStream(); - if (_tcpOptions.TlsOptions?.UseTls == true) + if (_tcpOptions.TlsOptions?.UseTls == true) + { + var targetHost = _tcpOptions.TlsOptions.TargetHost; + if (string.IsNullOrEmpty(targetHost)) { - var targetHost = _tcpOptions.TlsOptions.TargetHost; - if (string.IsNullOrEmpty(targetHost)) + if (_tcpOptions.RemoteEndpoint is DnsEndPoint dns) { - if (_tcpOptions.RemoteEndpoint is DnsEndPoint dns) - { - targetHost = dns.Host; - } + targetHost = dns.Host; } + } - SslStream sslStream; - if (_tcpOptions.TlsOptions.CertificateSelectionHandler != null) - { - sslStream = new SslStream( - networkStream, - false, - InternalUserCertificateValidationCallback, - InternalUserCertificateSelectionCallback); - } - else - { - // Use a different constructor depending on the options for MQTTnet so that we do not have - // to copy the exact same behavior of the selection handler. - sslStream = new SslStream( - networkStream, - false, - InternalUserCertificateValidationCallback); - } + SslStream sslStream; + if (_tcpOptions.TlsOptions.CertificateSelectionHandler != null) + { + sslStream = new SslStream( + networkStream, + false, + InternalUserCertificateValidationCallback, + InternalUserCertificateSelectionCallback); + } + else + { + // Use a different constructor depending on the options for MQTTnet so that we do not have + // to copy the exact same behavior of the selection handler. + sslStream = new SslStream( + networkStream, + false, + InternalUserCertificateValidationCallback); + } - try + try + { + var sslOptions = new SslClientAuthenticationOptions + { + ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols, + ClientCertificates = LoadCertificates(), + EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol, + CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors + ? X509RevocationMode.NoCheck + : _tcpOptions.TlsOptions.RevocationMode, + TargetHost = targetHost, + CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy, + EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy, + AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation + }; + + if (_tcpOptions.TlsOptions.TrustChain?.Count > 0) { - var sslOptions = new SslClientAuthenticationOptions + sslOptions.CertificateChainPolicy = new X509ChainPolicy { - ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols, - ClientCertificates = LoadCertificates(), - EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol, - CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors - ? X509RevocationMode.NoCheck - : _tcpOptions.TlsOptions.RevocationMode, - TargetHost = targetHost, - CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy, - EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy, - AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation + TrustMode = X509ChainTrustMode.CustomRootTrust, + VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown, + RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode }; - if (_tcpOptions.TlsOptions.TrustChain?.Count > 0) - { - sslOptions.CertificateChainPolicy = new X509ChainPolicy - { - TrustMode = X509ChainTrustMode.CustomRootTrust, - VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown, - RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode - }; - - sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain); - } - - await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); + sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain); } - catch - { - await sslStream.DisposeAsync().ConfigureAwait(false); - throw; - } - - _stream = sslStream; + await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); } - else + catch { - _stream = networkStream; + await sslStream.DisposeAsync().ConfigureAwait(false); + + throw; } - RemoteEndPoint = socket.RemoteEndPoint; + _stream = sslStream; } - catch (Exception) + else { - socket?.Dispose(); - throw; + _stream = networkStream; } - } - public Task DisconnectAsync(CancellationToken cancellationToken) + RemoteEndPoint = socket.RemoteEndPoint; + } + catch { - Dispose(); - return CompletedTask.Instance; + socket?.Dispose(); + throw; } + } + + public Task DisconnectAsync(CancellationToken cancellationToken) + { + Dispose(); + return CompletedTask.Instance; + } - public void Dispose() + public void Dispose() + { + // 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. + // https://stackoverflow.com/questions/3601521/should-i-manually-dispose-the-socket-after-closing-it + try { - // 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. - // https://stackoverflow.com/questions/3601521/should-i-manually-dispose-the-socket-after-closing-it - try - { - _stream?.Close(); - _stream?.Dispose(); - } - catch (ObjectDisposedException) - { - } - catch (NullReferenceException) - { - } - finally - { - _stream = null; - } + _stream?.Close(); + _stream?.Dispose(); } - - public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + catch (ObjectDisposedException) { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var stream = _stream; + } + catch (NullReferenceException) + { + } + finally + { + _stream = null; + } + } - if (stream == null) - { - return 0; - } + public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - if (!stream.CanRead) - { - return 0; - } + try + { + var stream = _stream; - return await stream.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false); - } - catch (ObjectDisposedException) + if (stream == null) { - // Indicate a graceful socket close. return 0; } - catch (IOException exception) - { - if (exception.InnerException is SocketException socketException) - { - ExceptionDispatchInfo.Capture(socketException).Throw(); - } - throw; + if (!stream.CanRead) + { + return 0; } - } - public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + return await stream.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false); + } + catch (ObjectDisposedException) { - cancellationToken.ThrowIfCancellationRequested(); - - try + // Indicate a graceful socket close. + return 0; + } + catch (IOException exception) + { + if (exception.InnerException is SocketException socketException) { - var stream = _stream; + ExceptionDispatchInfo.Capture(socketException).Throw(); + } - if (stream == null) - { - throw new MqttCommunicationException("The TCP connection is closed."); - } + throw; + } + } - foreach (var segment in buffer) - { - await stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false); - } - } - catch (ObjectDisposedException) + public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var stream = _stream; + + if (stream == null) { throw new MqttCommunicationException("The TCP connection is closed."); } - catch (IOException exception) - { - if (exception.InnerException is SocketException socketException) - { - ExceptionDispatchInfo.Capture(socketException).Throw(); - } - throw; + foreach (var segment in buffer) + { + await stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false); } } - - X509Certificate InternalUserCertificateSelectionCallback( - object sender, - string targetHost, - X509CertificateCollection localCertificates, - X509Certificate remoteCertificate, - string[] acceptableIssuers) + catch (ObjectDisposedException) + { + throw new MqttCommunicationException("The TCP connection is closed."); + } + catch (IOException exception) { - var certificateSelectionHandler = _tcpOptions?.TlsOptions?.CertificateSelectionHandler; - if (certificateSelectionHandler != null) + if (exception.InnerException is SocketException socketException) { - var eventArgs = new MqttClientCertificateSelectionEventArgs(targetHost, localCertificates, remoteCertificate, acceptableIssuers, _tcpOptions); - return certificateSelectionHandler(eventArgs); + ExceptionDispatchInfo.Capture(socketException).Throw(); } - if (localCertificates?.Count > 0) - { - return localCertificates[0]; - } + throw; + } + } - return null; + X509Certificate InternalUserCertificateSelectionCallback( + object sender, + string targetHost, + X509CertificateCollection localCertificates, + X509Certificate remoteCertificate, + string[] acceptableIssuers) + { + var certificateSelectionHandler = _tcpOptions?.TlsOptions?.CertificateSelectionHandler; + if (certificateSelectionHandler != null) + { + var eventArgs = new MqttClientCertificateSelectionEventArgs(targetHost, localCertificates, remoteCertificate, acceptableIssuers, _tcpOptions); + return certificateSelectionHandler(eventArgs); } - bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + if (localCertificates?.Count > 0) { - var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler; - if (certificateValidationHandler != null) - { - var eventArgs = new MqttClientCertificateValidationEventArgs(x509Certificate, chain, sslPolicyErrors, _tcpOptions); - return certificateValidationHandler(eventArgs); - } + return localCertificates[0]; + } - if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false) - { - sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors; - } + return null; + } - return sslPolicyErrors == SslPolicyErrors.None; + bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler; + if (certificateValidationHandler != null) + { + var eventArgs = new MqttClientCertificateValidationEventArgs(x509Certificate, chain, sslPolicyErrors, _tcpOptions); + return certificateValidationHandler(eventArgs); } - X509CertificateCollection LoadCertificates() + if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false) { - return _tcpOptions.TlsOptions.ClientCertificatesProvider?.GetCertificates(); + sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors; } + + return sslPolicyErrors == SslPolicyErrors.None; + } + + X509CertificateCollection LoadCertificates() + { + return _tcpOptions.TlsOptions.ClientCertificatesProvider?.GetCertificates(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index c83460110..701cc279f 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -12,230 +12,229 @@ using MQTTnet.Channel; using MQTTnet.Internal; -namespace MQTTnet.Implementations +namespace MQTTnet.Implementations; + +public sealed class MqttWebSocketChannel : IMqttChannel { - public sealed class MqttWebSocketChannel : IMqttChannel - { - readonly MqttClientWebSocketOptions _options; + readonly MqttClientWebSocketOptions _options; - AsyncLock _sendLock = new AsyncLock(); - WebSocket _webSocket; + AsyncLock _sendLock = new(); + WebSocket _webSocket; - public MqttWebSocketChannel(MqttClientWebSocketOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } + public MqttWebSocketChannel(MqttClientWebSocketOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } - public MqttWebSocketChannel(WebSocket webSocket, EndPoint remoteEndPoint, bool isSecureConnection, X509Certificate2 clientCertificate) - { - _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); + public MqttWebSocketChannel(WebSocket webSocket, EndPoint remoteEndPoint, bool isSecureConnection, X509Certificate2 clientCertificate) + { + _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); - RemoteEndPoint = remoteEndPoint; - IsSecureConnection = isSecureConnection; - ClientCertificate = clientCertificate; - } + RemoteEndPoint = remoteEndPoint; + IsSecureConnection = isSecureConnection; + ClientCertificate = clientCertificate; + } - public X509Certificate2 ClientCertificate { get; } + public X509Certificate2 ClientCertificate { get; } - public EndPoint RemoteEndPoint { get; } + public EndPoint RemoteEndPoint { get; } - public bool IsSecureConnection { get; private set; } + public bool IsSecureConnection { get; private set; } - public async Task ConnectAsync(CancellationToken cancellationToken) + public async Task ConnectAsync(CancellationToken cancellationToken) + { + var uri = _options.Uri; + if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) { - var uri = _options.Uri; - if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) - { - if (_options.TlsOptions?.UseTls == true) - { - uri = "wss://" + uri; - } - else - { - uri = "ws://" + uri; - } - } - - var clientWebSocket = new ClientWebSocket(); - try + if (_options.TlsOptions?.UseTls == true) { - SetupClientWebSocket(clientWebSocket); - - await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false); + uri = "wss://" + uri; } - catch + else { - // Prevent a memory leak when always creating new instance which will fail while connecting. - clientWebSocket.Dispose(); - throw; + uri = "ws://" + uri; } - - _webSocket = clientWebSocket; - IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase); } - public async Task DisconnectAsync(CancellationToken cancellationToken) + var clientWebSocket = new ClientWebSocket(); + try { - if (_webSocket == null) - { - return; - } + SetupClientWebSocket(clientWebSocket); - if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting) - { - await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); - } - - Cleanup(); + await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false); } - - public void Dispose() + catch { - Cleanup(); + // Prevent a memory leak when always creating new instance which will fail while connecting. + clientWebSocket.Dispose(); + throw; } - public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + _webSocket = clientWebSocket; + IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase); + } + + public async Task DisconnectAsync(CancellationToken cancellationToken) + { + if (_webSocket == null) { - var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, offset, count), cancellationToken).ConfigureAwait(false); - return response.Count; + return; } - public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + if (_webSocket.State is WebSocketState.Open or WebSocketState.Connecting) { - cancellationToken.ThrowIfCancellationRequested(); - - // MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1]. - // A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2]. - long length = buffer.Length; - foreach (var segment in buffer) - { - length -= segment.Length; - bool endOfPacket = isEndOfPacket && length == 0; - await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, endOfPacket, cancellationToken).ConfigureAwait(false); - } + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); } - void Cleanup() + Cleanup(); + } + + public void Dispose() + { + Cleanup(); + } + + public async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, offset, count), cancellationToken).ConfigureAwait(false); + return response.Count; + } + + public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1]. + // A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2]. + var length = buffer.Length; + foreach (var segment in buffer) { - _sendLock?.Dispose(); - _sendLock = null; + length -= segment.Length; + bool endOfPacket = isEndOfPacket && length == 0; + await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, endOfPacket, cancellationToken).ConfigureAwait(false); + } + } - try - { - _webSocket?.Dispose(); - } - catch (ObjectDisposedException) - { - } - finally - { - _webSocket = null; - } + void Cleanup() + { + _sendLock?.Dispose(); + _sendLock = null; + + try + { + _webSocket?.Dispose(); } + catch (ObjectDisposedException) + { + } + finally + { + _webSocket = null; + } + } - IWebProxy CreateProxy() + WebProxy CreateProxy() + { + if (string.IsNullOrEmpty(_options.ProxyOptions?.Address)) { - if (string.IsNullOrEmpty(_options.ProxyOptions?.Address)) - { - return null; - } + return null; + } - var proxyUri = new Uri(_options.ProxyOptions.Address); - WebProxy webProxy; + var proxyUri = new Uri(_options.ProxyOptions.Address); + WebProxy webProxy; - if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password)) - { - var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain); - webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials); - } - else - { - webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList); - } + if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password)) + { + var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain); + webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials); + } + else + { + webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList); + } - if (_options.ProxyOptions.UseDefaultCredentials) - { - // Only update the property if required because setting it to false will alter - // the used credentials internally! - webProxy.UseDefaultCredentials = true; - } + if (_options.ProxyOptions.UseDefaultCredentials) + { + // Only update the property if required because setting it to false will alter + // the used credentials internally! + webProxy.UseDefaultCredentials = true; + } - return webProxy; + return webProxy; + } + + void SetupClientWebSocket(ClientWebSocket clientWebSocket) + { + if (_options.ProxyOptions != null) + { + clientWebSocket.Options.Proxy = CreateProxy(); } - void SetupClientWebSocket(ClientWebSocket clientWebSocket) + if (_options.RequestHeaders != null) { - if (_options.ProxyOptions != null) + foreach (var requestHeader in _options.RequestHeaders) { - clientWebSocket.Options.Proxy = CreateProxy(); + clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); } + } - if (_options.RequestHeaders != null) + if (_options.SubProtocols != null) + { + foreach (var subProtocol in _options.SubProtocols) { - foreach (var requestHeader in _options.RequestHeaders) - { - clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); - } + clientWebSocket.Options.AddSubProtocol(subProtocol); } + } - if (_options.SubProtocols != null) - { - foreach (var subProtocol in _options.SubProtocols) - { - clientWebSocket.Options.AddSubProtocol(subProtocol); - } - } + if (_options.CookieContainer != null) + { + clientWebSocket.Options.Cookies = _options.CookieContainer; + } - if (_options.CookieContainer != null) + if (_options.TlsOptions?.UseTls == true) + { + var certificates = _options.TlsOptions?.ClientCertificatesProvider?.GetCertificates(); + if (certificates?.Count > 0) { - clientWebSocket.Options.Cookies = _options.CookieContainer; + clientWebSocket.Options.ClientCertificates = certificates; } + } - if (_options.TlsOptions?.UseTls == true) - { - var certificates = _options.TlsOptions?.ClientCertificatesProvider?.GetCertificates(); - if (certificates?.Count > 0) - { - clientWebSocket.Options.ClientCertificates = certificates; - } - } + if (_options.DangerousDeflateOptions != null) + clientWebSocket.Options.DangerousDeflateOptions = _options.DangerousDeflateOptions; - if (_options.DangerousDeflateOptions != null) - clientWebSocket.Options.DangerousDeflateOptions = _options.DangerousDeflateOptions; + // Only set the value if it is actually true. This property is not supported on all platforms + // and will throw a _PlatformNotSupported_ (i.e. WASM) exception when being used regardless of the actual value. + if (_options.UseDefaultCredentials) + { + clientWebSocket.Options.UseDefaultCredentials = _options.UseDefaultCredentials; + } - // Only set the value if it is actually true. This property is not supported on all platforms - // and will throw a _PlatformNotSupported_ (i.e. WASM) exception when being used regardless of the actual value. - if (_options.UseDefaultCredentials) - { - clientWebSocket.Options.UseDefaultCredentials = _options.UseDefaultCredentials; - } + if (_options.KeepAliveInterval != WebSocket.DefaultKeepAliveInterval) + { + clientWebSocket.Options.KeepAliveInterval = _options.KeepAliveInterval; + } - if (_options.KeepAliveInterval != WebSocket.DefaultKeepAliveInterval) - { - clientWebSocket.Options.KeepAliveInterval = _options.KeepAliveInterval; - } + if (_options.Credentials != null) + { + clientWebSocket.Options.Credentials = _options.Credentials; + } - if (_options.Credentials != null) + var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler; + if (certificateValidationHandler != null) + { + clientWebSocket.Options.RemoteCertificateValidationCallback = (_, certificate, chain, sslPolicyErrors) => { - clientWebSocket.Options.Credentials = _options.Credentials; - } + // TODO: Find a way to add client options to same callback. Problem is that they have a different type. + var context = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _options); + return certificateValidationHandler(context); + }; - var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler; - if (certificateValidationHandler != null) + var certificateSelectionHandler = _options.TlsOptions?.CertificateSelectionHandler; + if (certificateSelectionHandler != null) { - clientWebSocket.Options.RemoteCertificateValidationCallback = (_, certificate, chain, sslPolicyErrors) => - { - // TODO: Find a way to add client options to same callback. Problem is that they have a different type. - var context = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _options); - return certificateValidationHandler(context); - }; - - var certificateSelectionHandler = _options.TlsOptions?.CertificateSelectionHandler; - if (certificateSelectionHandler != null) - { - throw new NotSupportedException("Remote certificate selection callback is not supported for WebSocket connections."); - } + throw new NotSupportedException("Remote certificate selection callback is not supported for WebSocket connections."); } } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncEvent.cs b/Source/MQTTnet/Internal/AsyncEvent.cs index cd38105b3..c86073e31 100644 --- a/Source/MQTTnet/Internal/AsyncEvent.cs +++ b/Source/MQTTnet/Internal/AsyncEvent.cs @@ -7,105 +7,104 @@ using System.Threading.Tasks; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public sealed class AsyncEvent where TEventArgs : EventArgs { - public sealed class AsyncEvent where TEventArgs : EventArgs + readonly List> _handlers = []; + + ICollection> _handlersForInvoke; + + public AsyncEvent() { - readonly List> _handlers = new List>(); + _handlersForInvoke = _handlers; + } - ICollection> _handlersForInvoke; + // Track the existence of handlers in a separate field so that checking it all the time will not + // require locking the actual list (_handlers). + public bool HasHandlers { get; private set; } - public AsyncEvent() + public void AddHandler(Func handler) + { + ArgumentNullException.ThrowIfNull(handler); + + lock (_handlers) { - _handlersForInvoke = _handlers; + _handlers.Add(new AsyncEventInvocator(null, handler)); + + HasHandlers = true; + _handlersForInvoke = new List>(_handlers); } + } - // Track the existence of handlers in a separate field so that checking it all the time will not - // require locking the actual list (_handlers). - public bool HasHandlers { get; private set; } + public void AddHandler(Action handler) + { + ArgumentNullException.ThrowIfNull(handler); - public void AddHandler(Func handler) + lock (_handlers) { - ArgumentNullException.ThrowIfNull(handler); + _handlers.Add(new AsyncEventInvocator(handler, null)); - lock (_handlers) - { - _handlers.Add(new AsyncEventInvocator(null, handler)); - - HasHandlers = true; - _handlersForInvoke = new List>(_handlers); - } + HasHandlers = true; + _handlersForInvoke = new List>(_handlers); } + } - public void AddHandler(Action handler) + public async Task InvokeAsync(TEventArgs eventArgs) + { + if (!HasHandlers) { - ArgumentNullException.ThrowIfNull(handler); - - lock (_handlers) - { - _handlers.Add(new AsyncEventInvocator(handler, null)); - - HasHandlers = true; - _handlersForInvoke = new List>(_handlers); - } + return; } - public async Task InvokeAsync(TEventArgs eventArgs) + // Adding or removing handlers will produce a new list instance all the time. + // So locking here is not required since only the reference to an immutable list + // of handlers is used. + var handlers = _handlersForInvoke; + foreach (var handler in handlers) { - if (!HasHandlers) - { - return; - } - - // Adding or removing handlers will produce a new list instance all the time. - // So locking here is not required since only the reference to an immutable list - // of handlers is used. - var handlers = _handlersForInvoke; - foreach (var handler in handlers) - { - await handler.InvokeAsync(eventArgs).ConfigureAwait(false); - } + await handler.InvokeAsync(eventArgs).ConfigureAwait(false); } + } - public void RemoveHandler(Func handler) - { - ArgumentNullException.ThrowIfNull(handler); + public void RemoveHandler(Func handler) + { + ArgumentNullException.ThrowIfNull(handler); - lock (_handlers) - { - _handlers.RemoveAll(h => h.WrapsHandler(handler)); + lock (_handlers) + { + _handlers.RemoveAll(h => h.WrapsHandler(handler)); - HasHandlers = _handlers.Count > 0; - _handlersForInvoke = new List>(_handlers); - } + HasHandlers = _handlers.Count > 0; + _handlersForInvoke = [.._handlers]; } + } - public void RemoveHandler(Action handler) - { - ArgumentNullException.ThrowIfNull(handler); + public void RemoveHandler(Action handler) + { + ArgumentNullException.ThrowIfNull(handler); - lock (_handlers) - { - _handlers.RemoveAll(h => h.WrapsHandler(handler)); + lock (_handlers) + { + _handlers.RemoveAll(h => h.WrapsHandler(handler)); - HasHandlers = _handlers.Count > 0; - _handlersForInvoke = new List>(_handlers); - } + HasHandlers = _handlers.Count > 0; + _handlersForInvoke = [.._handlers]; } + } + + public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger) + { + ArgumentNullException.ThrowIfNull(eventArgs); + ArgumentNullException.ThrowIfNull(logger); - public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger) + try + { + await InvokeAsync(eventArgs).ConfigureAwait(false); + } + catch (Exception exception) { - ArgumentNullException.ThrowIfNull(eventArgs); - ArgumentNullException.ThrowIfNull(logger); - - try - { - await InvokeAsync(eventArgs).ConfigureAwait(false); - } - catch (Exception exception) - { - logger.Warning(exception, $"Error while invoking event with arguments of type {typeof(TEventArgs)}."); - } + logger.Warning(exception, $"Error while invoking event with arguments of type {typeof(TEventArgs)}."); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncEventInvocator.cs b/Source/MQTTnet/Internal/AsyncEventInvocator.cs index f344cb1e6..d25272452 100644 --- a/Source/MQTTnet/Internal/AsyncEventInvocator.cs +++ b/Source/MQTTnet/Internal/AsyncEventInvocator.cs @@ -4,47 +4,45 @@ using System; using System.Threading.Tasks; -using MQTTnet.Implementations; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public readonly struct AsyncEventInvocator { - public readonly struct AsyncEventInvocator + readonly Action _handler; + readonly Func _asyncHandler; + + public AsyncEventInvocator(Action handler, Func asyncHandler) { - readonly Action _handler; - readonly Func _asyncHandler; - - public AsyncEventInvocator(Action handler, Func asyncHandler) - { - _handler = handler; - _asyncHandler = asyncHandler; - } + _handler = handler; + _asyncHandler = asyncHandler; + } - public bool WrapsHandler(Action handler) - { - // Do not use ReferenceEquals! It will not work with delegates. - return handler == _handler; - } - - public bool WrapsHandler(Func handler) + public bool WrapsHandler(Action handler1) + { + // Do not use ReferenceEquals! It will not work with delegates. + return handler1 == _handler; + } + + public bool WrapsHandler(Func handler) + { + // Do not use ReferenceEquals! It will not work with delegates. + return handler == _asyncHandler; + } + + public Task InvokeAsync(TEventArgs eventArgs) + { + if (_handler != null) { - // Do not use ReferenceEquals! It will not work with delegates. - return handler == _asyncHandler; + _handler(eventArgs); + return CompletedTask.Instance; } - - public Task InvokeAsync(TEventArgs eventArgs) + + if (_asyncHandler != null) { - if (_handler != null) - { - _handler.Invoke(eventArgs); - return CompletedTask.Instance; - } - - if (_asyncHandler != null) - { - return _asyncHandler.Invoke(eventArgs); - } - - throw new InvalidOperationException(); + return _asyncHandler(eventArgs); } + + throw new InvalidOperationException(); } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncLock.cs b/Source/MQTTnet/Internal/AsyncLock.cs index 0d129b102..de646ede8 100644 --- a/Source/MQTTnet/Internal/AsyncLock.cs +++ b/Source/MQTTnet/Internal/AsyncLock.cs @@ -4,157 +4,152 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public sealed class AsyncLock : IDisposable { - public sealed class AsyncLock : IDisposable - { - readonly Task _completedTask; - readonly IDisposable _releaser; - readonly object _syncRoot = new object(); - readonly Queue _waiters = new Queue(64); + readonly Task _completedTask; + readonly IDisposable _releaser; + readonly object _syncRoot = new(); + readonly Queue _waiters = new(64); - volatile bool _isDisposed; - bool _isLocked; + volatile bool _isDisposed; + bool _isLocked; - public AsyncLock() - { - _releaser = new Releaser(this); - _completedTask = Task.FromResult(_releaser); - } + public AsyncLock() + { + _releaser = new Releaser(this); + _completedTask = Task.FromResult(_releaser); + } - public void Dispose() + public void Dispose() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _isDisposed = true; + _isDisposed = true; - while (_waiters.Any()) - { - _waiters.Dequeue().Dispose(); - } + while (_waiters.Count != 0) + { + _waiters.Dequeue().Dispose(); } } + } - public Task EnterAsync(CancellationToken cancellationToken = default) + public Task EnterAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + ObjectDisposedException.ThrowIf(_isDisposed, this); + + lock (_syncRoot) { - cancellationToken.ThrowIfCancellationRequested(); - - if (_isDisposed) + if (!_isLocked) { - throw new ObjectDisposedException(nameof(AsyncLock)); + _isLocked = true; + return _completedTask; } - lock (_syncRoot) - { - if (!_isLocked) - { - _isLocked = true; - return _completedTask; - } - - var waiter = new AsyncLockWaiter(cancellationToken); - _waiters.Enqueue(waiter); + var waiter = new AsyncLockWaiter(cancellationToken); + _waiters.Enqueue(waiter); - return waiter.Task; - } + return waiter.Task; } + } - void Release() + void Release() + { + lock (_syncRoot) { - lock (_syncRoot) + if (_isDisposed) { - if (_isDisposed) - { - // All waiters have been canceled with a ObjectDisposedException. - // So there is nothing left to do. - return; - } + // All waiters have been canceled with a ObjectDisposedException. + // So there is nothing left to do. + return; + } - // Assume that there is no waiter left first. - _isLocked = false; + // Assume that there is no waiter left first. + _isLocked = false; - // Try to find the next waiter which can be approved. - // Some of them might be canceled already so it is not - // guaranteed that the very next waiter is the correct one. - while (_waiters.Any()) + // Try to find the next waiter which can be approved. + // Some of them might be canceled already so it is not + // guaranteed that the very next waiter is the correct one. + while (_waiters.Count != 0) + { + var waiter = _waiters.Dequeue(); + var isApproved = waiter.Approve(_releaser); + waiter.Dispose(); + + if (isApproved) { - var waiter = _waiters.Dequeue(); - var isApproved = waiter.Approve(_releaser); - waiter.Dispose(); - - if (isApproved) - { - _isLocked = true; - return; - } + _isLocked = true; + return; } } } + } + + sealed class AsyncLockWaiter : IDisposable + { + readonly CancellationTokenRegistration _cancellationRegistration; + readonly bool _hasCancellationRegistration; + readonly AsyncTaskCompletionSource _promise = new AsyncTaskCompletionSource(); - sealed class AsyncLockWaiter : IDisposable + public AsyncLockWaiter(CancellationToken cancellationToken) { - readonly CancellationTokenRegistration _cancellationRegistration; - readonly bool _hasCancellationRegistration; - readonly AsyncTaskCompletionSource _promise = new AsyncTaskCompletionSource(); + cancellationToken.ThrowIfCancellationRequested(); - public AsyncLockWaiter(CancellationToken cancellationToken) + if (cancellationToken.CanBeCanceled) { - cancellationToken.ThrowIfCancellationRequested(); - - if (cancellationToken.CanBeCanceled) - { - _cancellationRegistration = cancellationToken.Register(Cancel); - _hasCancellationRegistration = true; - } + _cancellationRegistration = cancellationToken.Register(Cancel); + _hasCancellationRegistration = true; } + } - public Task Task => _promise.Task; - - public bool Approve(IDisposable scope) - { - ArgumentNullException.ThrowIfNull(scope); - - if (_promise.Task.IsCompleted) - { - return false; - } + public Task Task => _promise.Task; - return _promise.TrySetResult(scope); - } + public bool Approve(IDisposable scope) + { + ArgumentNullException.ThrowIfNull(scope); - public void Dispose() + if (_promise.Task.IsCompleted) { - if (_hasCancellationRegistration) - { - _cancellationRegistration.Dispose(); - } - - _promise.TrySetException(new ObjectDisposedException(nameof(AsyncLockWaiter))); + return false; } - void Cancel() + return _promise.TrySetResult(scope); + } + + public void Dispose() + { + if (_hasCancellationRegistration) { - _promise.TrySetCanceled(); + _cancellationRegistration.Dispose(); } + + _promise.TrySetException(new ObjectDisposedException(nameof(AsyncLockWaiter))); } - readonly struct Releaser : IDisposable + void Cancel() { - readonly AsyncLock _asyncLock; + _promise.TrySetCanceled(); + } + } - public Releaser(AsyncLock asyncLock) - { - _asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock)); - } + readonly struct Releaser : IDisposable + { + readonly AsyncLock _asyncLock; - public void Dispose() - { - _asyncLock.Release(); - } + public Releaser(AsyncLock asyncLock) + { + _asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock)); + } + + public void Dispose() + { + _asyncLock.Release(); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncQueue.cs b/Source/MQTTnet/Internal/AsyncQueue.cs index 8824b07bc..f86a09023 100644 --- a/Source/MQTTnet/Internal/AsyncQueue.cs +++ b/Source/MQTTnet/Internal/AsyncQueue.cs @@ -7,103 +7,104 @@ using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +#pragma warning disable CA1711 + +public sealed class AsyncQueue : IDisposable { - public sealed class AsyncQueue : IDisposable - { - readonly AsyncSignal _signal = new AsyncSignal(); - readonly object _syncRoot = new object(); + readonly AsyncSignal _signal = new(); + readonly object _syncRoot = new(); - ConcurrentQueue _queue = new ConcurrentQueue(); + readonly ConcurrentQueue _queue = []; - bool _isDisposed; + bool _isDisposed; - public int Count => _queue.Count; + public int Count => _queue.Count; - public void Clear() - { - _queue.Clear(); - } + public void Clear() + { + _queue.Clear(); + } - public void Dispose() + public void Dispose() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _signal.Dispose(); + _signal.Dispose(); - _isDisposed = true; + _isDisposed = true; - if (typeof(IDisposable).IsAssignableFrom(typeof(TItem))) + if (typeof(IDisposable).IsAssignableFrom(typeof(TItem))) + { + while (_queue.TryDequeue(out var item)) { - while (_queue.TryDequeue(out TItem item)) - { - (item as IDisposable).Dispose(); - } + (item as IDisposable)?.Dispose(); } } } + } - public void Enqueue(TItem item) + public void Enqueue(TItem item) + { + lock (_syncRoot) { - lock (_syncRoot) - { - _queue.Enqueue(item); - _signal.Set(); - } + _queue.Enqueue(item); + _signal.Set(); } + } - public AsyncQueueDequeueResult TryDequeue() + public AsyncQueueDequeueResult TryDequeue() + { + if (_queue.TryDequeue(out var item)) { - if (_queue.TryDequeue(out var item)) - { - return AsyncQueueDequeueResult.Success(item); - } - - return AsyncQueueDequeueResult.NonSuccess; + return new AsyncQueueDequeueResult(true, item); } - public async Task> TryDequeueAsync(CancellationToken cancellationToken) + return AsyncQueueDequeueResult.NonSuccess; + } + + public async Task> TryDequeueAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) { - while (!cancellationToken.IsCancellationRequested) + try { - try + Task task = null; + lock (_syncRoot) { - Task task = null; - lock (_syncRoot) - { - if (_isDisposed) - { - return AsyncQueueDequeueResult.NonSuccess; - } - - if (_queue.IsEmpty) - { - task = _signal.WaitAsync(cancellationToken); - } - } - - if (task != null) - { - await task.ConfigureAwait(false); - } - - if (cancellationToken.IsCancellationRequested) + if (_isDisposed) { return AsyncQueueDequeueResult.NonSuccess; } - if (_queue.TryDequeue(out var item)) + if (_queue.IsEmpty) { - return AsyncQueueDequeueResult.Success(item); + task = _signal.WaitAsync(cancellationToken); } } - catch (OperationCanceledException) + + if (task != null) + { + await task.ConfigureAwait(false); + } + + if (cancellationToken.IsCancellationRequested) { return AsyncQueueDequeueResult.NonSuccess; } - } - return AsyncQueueDequeueResult.NonSuccess; + if (_queue.TryDequeue(out var item)) + { + return new AsyncQueueDequeueResult(true, item); + } + } + catch (OperationCanceledException) + { + return AsyncQueueDequeueResult.NonSuccess; + } } + + return AsyncQueueDequeueResult.NonSuccess; } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs b/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs index 095432383..bdbf4f1bc 100644 --- a/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs +++ b/Source/MQTTnet/Internal/AsyncQueueDequeueResult.cs @@ -2,25 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Internal -{ - public sealed class AsyncQueueDequeueResult - { - public static readonly AsyncQueueDequeueResult NonSuccess = new AsyncQueueDequeueResult(false, default); - - public AsyncQueueDequeueResult(bool isSuccess, TItem item) - { - IsSuccess = isSuccess; - Item = item; - } +namespace MQTTnet.Internal; - public bool IsSuccess { get; } +public sealed class AsyncQueueDequeueResult(bool isSuccess, TItem item) +{ + public static readonly AsyncQueueDequeueResult NonSuccess = new(false, default); - public TItem Item { get; } + public bool IsSuccess { get; } = isSuccess; - public static AsyncQueueDequeueResult Success(TItem item) - { - return new AsyncQueueDequeueResult(true, item); - } - } + public TItem Item { get; } = item; } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncSignal.cs b/Source/MQTTnet/Internal/AsyncSignal.cs index bc3bcb861..d5f1589b4 100644 --- a/Source/MQTTnet/Internal/AsyncSignal.cs +++ b/Source/MQTTnet/Internal/AsyncSignal.cs @@ -6,150 +6,146 @@ using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public sealed class AsyncSignal : IDisposable { - public sealed class AsyncSignal : IDisposable - { - readonly object _syncRoot = new object(); + readonly object _syncRoot = new(); - bool _isDisposed; - bool _isSignaled; - AsyncSignalWaiter _waiter; + bool _isDisposed; + bool _isSignaled; + AsyncSignalWaiter _waiter; - public void Dispose() + public void Dispose() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _waiter?.Dispose(); - _waiter = null; + _waiter?.Dispose(); + _waiter = null; - _isDisposed = true; - } + _isDisposed = true; } + } - public void Set() + public void Set() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _isSignaled = true; + _isSignaled = true; - Cleanup(); + Cleanup(); - // If there is already a waiting task let it run. - if (_waiter != null) - { - _waiter.Approve(); - _waiter.Dispose(); - _waiter = null; + // If there is already a waiting task let it run. + if (_waiter != null) + { + _waiter.Approve(); + _waiter.Dispose(); + _waiter = null; - // Since we already got a waiter the signal must be reset right now! - _isSignaled = false; - } + // Since we already got a waiter the signal must be reset right now! + _isSignaled = false; } } + } - public Task WaitAsync(CancellationToken cancellationToken = default) + public Task WaitAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + lock (_syncRoot) { - cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); - lock (_syncRoot) - { - ThrowIfDisposed(); + Cleanup(); - Cleanup(); + if (_isSignaled) + { + _isSignaled = false; + return CompletedTask.Instance; + } - if (_isSignaled) + if (_waiter != null) + { + if (!_waiter.Task.IsCompleted) { - _isSignaled = false; - return CompletedTask.Instance; + throw new InvalidOperationException("Only one waiting task is permitted per async signal."); } - if (_waiter != null) - { - if (!_waiter.Task.IsCompleted) - { - throw new InvalidOperationException("Only one waiting task is permitted per async signal."); - } + _waiter.Dispose(); + } - _waiter.Dispose(); - } + _waiter = new AsyncSignalWaiter(cancellationToken); + return _waiter.Task; + } + } - _waiter = new AsyncSignalWaiter(cancellationToken); - return _waiter.Task; - } + void Cleanup() + { + // Cleanup if the previous waiter was cancelled. + if (_waiter != null && _waiter.Task.IsCanceled) + { + _waiter = null; } + } - void Cleanup() + void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + } + + sealed class AsyncSignalWaiter : IDisposable + { + readonly AsyncTaskCompletionSource _promise = new(); + + // ReSharper disable once FieldCanBeMadeReadOnly.Local + CancellationTokenRegistration _cancellationTokenRegistration; + + volatile bool _isCompleted; + + public AsyncSignalWaiter(CancellationToken cancellationToken) { - // Cleanup if the previous waiter was cancelled. - if (_waiter != null && _waiter.Task.IsCanceled) + if (cancellationToken.CanBeCanceled) { - _waiter = null; + _cancellationTokenRegistration = cancellationToken.Register(Cancel); } } - void ThrowIfDisposed() + public Task Task => _promise.Task; + + public void Approve() { - if (_isDisposed) + if (_isCompleted) { - throw new ObjectDisposedException(nameof(AsyncSignal)); + return; } + + _isCompleted = true; + _promise.TrySetResult(true); } - sealed class AsyncSignalWaiter : IDisposable + public void Dispose() { - readonly AsyncTaskCompletionSource _promise = new AsyncTaskCompletionSource(); - - // ReSharper disable once FieldCanBeMadeReadOnly.Local - CancellationTokenRegistration _cancellationTokenRegistration; - - volatile bool _isCompleted; + _cancellationTokenRegistration.Dispose(); - public AsyncSignalWaiter(CancellationToken cancellationToken) + if (_isCompleted) { - if (cancellationToken.CanBeCanceled) - { - _cancellationTokenRegistration = cancellationToken.Register(Cancel); - } + // Avoid allocation of _ObjectDisposedException_ which may not be used. + return; } - public Task Task => _promise.Task; - - public void Approve() - { - if (_isCompleted) - { - return; - } - - _isCompleted = true; - _promise.TrySetResult(true); - } + _isCompleted = true; + _promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter))); + } - public void Dispose() + void Cancel() + { + if (_isCompleted) { - _cancellationTokenRegistration.Dispose(); - - if (_isCompleted) - { - // Avoid allocation of _ObjectDisposedException_ which may not be used. - return; - } - - _isCompleted = true; - _promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter))); + return; } - void Cancel() - { - if (_isCompleted) - { - return; - } - - _isCompleted = true; - _promise.TrySetCanceled(); - } + _isCompleted = true; + _promise.TrySetCanceled(); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/AsyncTaskCompletionSource.cs b/Source/MQTTnet/Internal/AsyncTaskCompletionSource.cs index 7c0b9f4d9..f7f2092c5 100644 --- a/Source/MQTTnet/Internal/AsyncTaskCompletionSource.cs +++ b/Source/MQTTnet/Internal/AsyncTaskCompletionSource.cs @@ -5,32 +5,26 @@ using System; using System.Threading.Tasks; -namespace MQTTnet.Internal -{ - public sealed class AsyncTaskCompletionSource - { - readonly TaskCompletionSource _taskCompletionSource; +namespace MQTTnet.Internal; - public AsyncTaskCompletionSource() - { - _taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } +public sealed class AsyncTaskCompletionSource +{ + readonly TaskCompletionSource _taskCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously); - public Task Task => _taskCompletionSource.Task; + public Task Task => _taskCompletionSource.Task; - public void TrySetCanceled() - { - _taskCompletionSource.TrySetCanceled(); - } + public void TrySetCanceled() + { + _taskCompletionSource.TrySetCanceled(); + } - public void TrySetException(Exception exception) - { - _taskCompletionSource.TrySetException(exception); - } + public void TrySetException(Exception exception) + { + _taskCompletionSource.TrySetException(exception); + } - public bool TrySetResult(TResult result) - { - return _taskCompletionSource.TrySetResult(result); - } + public bool TrySetResult(TResult result) + { + return _taskCompletionSource.TrySetResult(result); } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/BlockingQueue.cs b/Source/MQTTnet/Internal/BlockingQueue.cs index e4122dfec..142c331fc 100644 --- a/Source/MQTTnet/Internal/BlockingQueue.cs +++ b/Source/MQTTnet/Internal/BlockingQueue.cs @@ -6,122 +6,123 @@ using System.Collections.Generic; using System.Threading; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +#pragma warning disable CA1711 + +public sealed class BlockingQueue : IDisposable { - public sealed class BlockingQueue : IDisposable - { - readonly object _syncRoot = new object(); - readonly LinkedList _items = new LinkedList(); + readonly LinkedList _items = []; + readonly object _syncRoot = new(); - ManualResetEventSlim _gate = new ManualResetEventSlim(false); + ManualResetEventSlim _gate = new(false); - public int Count + public int Count + { + get { - get + lock (_syncRoot) { - lock (_syncRoot) - { - return _items.Count; - } + return _items.Count; } } + } - public void Enqueue(TItem item) + public void Clear() + { + lock (_syncRoot) { - ArgumentNullException.ThrowIfNull(item); - - lock (_syncRoot) - { - _items.AddLast(item); - _gate?.Set(); - } + _items.Clear(); } + } - public TItem Dequeue(CancellationToken cancellationToken = default) + public TItem Dequeue(CancellationToken cancellationToken = default) + { + while (!cancellationToken.IsCancellationRequested) { - while (!cancellationToken.IsCancellationRequested) + lock (_syncRoot) { - lock (_syncRoot) + if (_items.Count > 0) { - if (_items.Count > 0) - { - var item = _items.First.Value; - _items.RemoveFirst(); - - return item; - } - - if (_items.Count == 0) - { - _gate?.Reset(); - } + var item = _items.First!.Value; + _items.RemoveFirst(); + + return item; } - _gate?.Wait(cancellationToken); + if (_items.Count == 0) + { + _gate?.Reset(); + } } - throw new OperationCanceledException(); + _gate?.Wait(cancellationToken); } - public TItem PeekAndWait(CancellationToken cancellationToken = default) - { - while (!cancellationToken.IsCancellationRequested) - { - lock (_syncRoot) - { - if (_items.Count > 0) - { - return _items.First.Value; - } - - if (_items.Count == 0) - { - _gate?.Reset(); - } - } + throw new OperationCanceledException(); + } - _gate?.Wait(cancellationToken); - } + public void Dispose() + { + _gate?.Dispose(); + _gate = null; + } - throw new OperationCanceledException(); - } + public void Enqueue(TItem item) + { + ArgumentNullException.ThrowIfNull(item); - public void RemoveFirst(Predicate match) + lock (_syncRoot) { - ArgumentNullException.ThrowIfNull(match); + _items.AddLast(item); + _gate?.Set(); + } + } + public TItem PeekAndWait(CancellationToken cancellationToken = default) + { + while (!cancellationToken.IsCancellationRequested) + { lock (_syncRoot) { - if (_items.Count > 0 && match(_items.First.Value)) + if (_items.Count > 0) { - _items.RemoveFirst(); + return _items.First!.Value; + } + + if (_items.Count == 0) + { + _gate?.Reset(); } } + + _gate?.Wait(cancellationToken); } - public TItem RemoveFirst() - { - lock (_syncRoot) - { - var item = _items.First; - _items.RemoveFirst(); + throw new OperationCanceledException(); + } - return item.Value; - } - } + public void RemoveFirst(Predicate match) + { + ArgumentNullException.ThrowIfNull(match); - public void Clear() + lock (_syncRoot) { - lock (_syncRoot) + if (_items.Count > 0 && match(_items.First!.Value)) { - _items.Clear(); + _items.RemoveFirst(); } } + } - public void Dispose() + public TItem RemoveFirst() + { + lock (_syncRoot) { - _gate?.Dispose(); - _gate = null; + var item = _items.First; + _items.RemoveFirst(); + + return item!.Value; } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs b/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs index 94c3febce..ed076d542 100644 --- a/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs +++ b/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs @@ -5,33 +5,32 @@ using System; using System.Threading; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public static class CancellationTokenSourceExtensions { - public static class CancellationTokenSourceExtensions + public static bool TryCancel(this CancellationTokenSource cancellationTokenSource, bool throwOnFirstException = false) { - public static bool TryCancel(this CancellationTokenSource cancellationTokenSource, bool throwOnFirstException = false) + if (cancellationTokenSource == null) { - if (cancellationTokenSource == null) - { - return false; - } - - try - { - // Checking the _IsCancellationRequested_ here will not throw an - // "ObjectDisposedException" as the getter of the property "Token" will do! - if (cancellationTokenSource.IsCancellationRequested) - { - return false; - } + return false; + } - cancellationTokenSource.Cancel(throwOnFirstException); - return true; - } - catch (ObjectDisposedException) + try + { + // Checking the _IsCancellationRequested_ here will not throw an + // "ObjectDisposedException" as the getter of the property "Token" will do! + if (cancellationTokenSource.IsCancellationRequested) { return false; } + + cancellationTokenSource.Cancel(throwOnFirstException); + return true; + } + catch (ObjectDisposedException) + { + return false; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/CompletedTask.cs b/Source/MQTTnet/Internal/CompletedTask.cs index 4bd129eb2..202f2c925 100644 --- a/Source/MQTTnet/Internal/CompletedTask.cs +++ b/Source/MQTTnet/Internal/CompletedTask.cs @@ -4,10 +4,9 @@ using System.Threading.Tasks; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public static class CompletedTask { - public static class CompletedTask - { - public static readonly Task Instance = Task.CompletedTask; - } + public static readonly Task Instance = Task.CompletedTask; } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/Disposable.cs b/Source/MQTTnet/Internal/Disposable.cs index 0adb34785..a35a32143 100644 --- a/Source/MQTTnet/Internal/Disposable.cs +++ b/Source/MQTTnet/Internal/Disposable.cs @@ -4,38 +4,34 @@ using System; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public abstract class Disposable : IDisposable { - public abstract class Disposable : IDisposable + protected bool IsDisposed { get; private set; } + + protected void ThrowIfDisposed() { - protected bool IsDisposed { get; private set; } + ObjectDisposedException.ThrowIf(IsDisposed, GetType()); + } - protected void ThrowIfDisposed() - { - if (IsDisposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } + protected virtual void Dispose(bool disposing) + { + } - protected virtual void Dispose(bool disposing) - { - } + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // This code added to correctly implement the disposable pattern. - public void Dispose() + if (IsDisposed) { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - - if (IsDisposed) - { - return; - } + return; + } - IsDisposed = true; + IsDisposed = true; - Dispose(true); - GC.SuppressFinalize(this); - } + Dispose(true); + GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Internal/EmptyBuffer.cs b/Source/MQTTnet/Internal/EmptyBuffer.cs index eb0506e94..dd319aefa 100644 --- a/Source/MQTTnet/Internal/EmptyBuffer.cs +++ b/Source/MQTTnet/Internal/EmptyBuffer.cs @@ -5,14 +5,13 @@ using System; using System.Buffers; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public static class EmptyBuffer { - public static class EmptyBuffer - { - public static readonly byte[] Array = System.Array.Empty(); + public static readonly byte[] Array = []; - public static readonly ArraySegment ArraySegment = new ArraySegment(Array, 0, 0); + public static readonly ArraySegment ArraySegment = new(Array, 0, 0); - public static readonly ReadOnlySequence ReadOnlySequence = ReadOnlySequence.Empty; - } + public static readonly ReadOnlySequence ReadOnlySequence = ReadOnlySequence.Empty; } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/MqttClientResultFactory.cs b/Source/MQTTnet/Internal/MqttClientResultFactory.cs deleted file mode 100644 index bba11ab1b..000000000 --- a/Source/MQTTnet/Internal/MqttClientResultFactory.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace MQTTnet.Internal; - -public static class MqttClientResultFactory -{ - public static readonly MqttClientConnectResultFactory ConnectResult = new(); - public static readonly MqttClientPublishResultFactory PublishResult = new(); - public static readonly MqttClientSubscribeResultFactory SubscribeResult = new(); - public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new(); -} \ No newline at end of file diff --git a/Source/MQTTnet/Internal/MqttMemoryHelper.cs b/Source/MQTTnet/Internal/MqttMemoryHelper.cs index 8f3b10083..3e61495bf 100644 --- a/Source/MQTTnet/Internal/MqttMemoryHelper.cs +++ b/Source/MQTTnet/Internal/MqttMemoryHelper.cs @@ -2,86 +2,85 @@ using System.Buffers; using System.Runtime.CompilerServices; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public static class MqttMemoryHelper { - public static class MqttMemoryHelper + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length) - { - source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length)); - } + source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length)); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(ReadOnlySequence sequence, int sourceIndex, byte[] destination, int destinationIndex, int length) - { - sequence.Slice(sourceIndex).CopyTo(destination.AsSpan(destinationIndex, length)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(ReadOnlySequence sequence, int sourceIndex, byte[] destination, int destinationIndex, int length) + { + sequence.Slice(sourceIndex).CopyTo(destination.AsSpan(destinationIndex, length)); + } - public static bool SequenceEqual(ArraySegment source, ArraySegment target) + public static bool SequenceEqual(ArraySegment source, ArraySegment target) + { + return source.AsSpan().SequenceEqual(target); + } + + public static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence target) + { + if (source.Length != target.Length) { - return source.AsSpan().SequenceEqual(target); + return false; } - public static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence target) - { - if (source.Length != target.Length) - { - return false; - } + long comparedLength = 0; + long length = source.Length; - long comparedLength = 0; - long length = source.Length; + int sourceOffset = 0; + int targetOffset = 0; - int sourceOffset = 0; - int targetOffset = 0; + var sourceEnumerator = source.GetEnumerator(); + var targetEnumerator = target.GetEnumerator(); - var sourceEnumerator = source.GetEnumerator(); - var targetEnumerator = target.GetEnumerator(); + var sourceSegment = sourceEnumerator.Current; + var targetSegment = targetEnumerator.Current; - ReadOnlyMemory sourceSegment = sourceEnumerator.Current; - ReadOnlyMemory targetSegment = targetEnumerator.Current; + while (true) + { + var compareLength = Math.Min(sourceSegment.Length - sourceOffset, targetSegment.Length - targetOffset); + + if (compareLength > 0 && + !sourceSegment.Span.Slice(sourceOffset, compareLength).SequenceEqual(targetSegment.Span.Slice(targetOffset, compareLength))) + { + return false; + } - while (true) + comparedLength += compareLength; + if (comparedLength >= length) { - int compareLength = Math.Min(sourceSegment.Length - sourceOffset, targetSegment.Length - targetOffset); + return true; + } - if (compareLength > 0 && - !sourceSegment.Span.Slice(sourceOffset, compareLength).SequenceEqual(targetSegment.Span.Slice(targetOffset, compareLength))) + sourceOffset += compareLength; + if (sourceOffset >= sourceSegment.Length) + { + if (!sourceEnumerator.MoveNext()) { return false; } - comparedLength += compareLength; - if (comparedLength >= length) - { - return true; - } + sourceSegment = sourceEnumerator.Current; + sourceOffset = 0; + } - sourceOffset += compareLength; - if (sourceOffset >= sourceSegment.Length) + targetOffset += compareLength; + if (targetOffset >= targetSegment.Length) + { + if (!targetEnumerator.MoveNext()) { - if (!sourceEnumerator.MoveNext()) - { - return false; - } - - sourceSegment = sourceEnumerator.Current; - sourceOffset = 0; + return false; } - targetOffset += compareLength; - if (targetOffset >= targetSegment.Length) - { - if (!targetEnumerator.MoveNext()) - { - return false; - } - - targetSegment = targetEnumerator.Current; - targetOffset = 0; - } + targetSegment = targetEnumerator.Current; + targetOffset = 0; } } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Internal/MqttPacketBus.cs b/Source/MQTTnet/Internal/MqttPacketBus.cs index 46e2b3493..e0b0652e3 100644 --- a/Source/MQTTnet/Internal/MqttPacketBus.cs +++ b/Source/MQTTnet/Internal/MqttPacketBus.cs @@ -9,156 +9,155 @@ using System.Threading.Tasks; using MQTTnet.Packets; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public sealed class MqttPacketBus : IDisposable { - public sealed class MqttPacketBus : IDisposable - { - readonly LinkedList[] _partitions = - { - new LinkedList(), - new LinkedList(), - new LinkedList() - }; + readonly LinkedList[] _partitions = + [ + [], + [], + [] + ]; - readonly AsyncSignal _signal = new AsyncSignal(); - readonly object _syncRoot = new object(); + readonly AsyncSignal _signal = new(); + readonly object _syncRoot = new(); - int _activePartition = (int)MqttPacketBusPartition.Health; + int _activePartition = (int)MqttPacketBusPartition.Health; - public int TotalItemsCount + public int TotalItemsCount + { + get { - get + lock (_syncRoot) { - lock (_syncRoot) - { - return _partitions.Sum(p => p.Count); - } + return _partitions.Sum(p => p.Count); } } + } - public void Clear() + public void Clear() + { + lock (_syncRoot) { - lock (_syncRoot) + foreach (var partition in _partitions) { - foreach (var partition in _partitions) - { - partition.Clear(); - } + partition.Clear(); } } + } - public async Task DequeueItemAsync(CancellationToken cancellationToken) + public async Task DequeueItemAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) { - while (!cancellationToken.IsCancellationRequested) + lock (_syncRoot) { - lock (_syncRoot) + for (var i = 0; i < 3; i++) { - for (var i = 0; i < 3; i++) - { - // Iterate through the partitions in order to ensure processing of health packets - // even if lots of data packets are enqueued. + // Iterate through the partitions in order to ensure processing of health packets + // even if lots of data packets are enqueued. - // Partition | Messages (left = oldest). - // DATA | [#]######################### - // CONTROL | [#]############# - // HEALTH | [#]#### + // Partition | Messages (left = oldest). + // DATA | [#]######################### + // CONTROL | [#]############# + // HEALTH | [#]#### - // In this sample the 3 oldest messages from the partitions are processed in a row. - // Then the next 3 from all 3 partitions. + // In this sample the 3 oldest messages from the partitions are processed in a row. + // Then the next 3 from all 3 partitions. - MoveActivePartition(); + MoveActivePartition(); - var activePartition = _partitions[_activePartition]; + var activePartition = _partitions[_activePartition]; - if (activePartition.First != null) - { - var item = activePartition.First; - activePartition.RemoveFirst(); + if (activePartition.First != null) + { + var item = activePartition.First; + activePartition.RemoveFirst(); - return item.Value; - } + return item.Value; } } + } - // No partition contains data so that we have to wait and put - // the worker back to the thread pool. - try - { - await _signal.WaitAsync(cancellationToken).ConfigureAwait(false); - } - catch (ObjectDisposedException) - { - // The cancelled token should "hide" the disposal of the signal. - cancellationToken.ThrowIfCancellationRequested(); - throw; - } + // No partition contains data so that we have to wait and put + // the worker back to the thread pool. + try + { + await _signal.WaitAsync(cancellationToken).ConfigureAwait(false); + } + catch (ObjectDisposedException) + { + // The cancelled token should "hide" the disposal of the signal. + cancellationToken.ThrowIfCancellationRequested(); + throw; } + } - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - throw new InvalidOperationException("MqttPacketBus is broken."); - } + throw new InvalidOperationException("MqttPacketBus is broken."); + } - public void Dispose() - { - _signal.Dispose(); - } + public void Dispose() + { + _signal.Dispose(); + } - public MqttPacketBusItem DropFirstItem(MqttPacketBusPartition partition) + public MqttPacketBusItem DropFirstItem(MqttPacketBusPartition partition) + { + lock (_syncRoot) { - lock (_syncRoot) - { - var partitionInstance = _partitions[(int)partition]; + var partitionInstance = _partitions[(int)partition]; - if (partitionInstance.Count > 0) - { - var firstItem = partitionInstance.First.Value; - partitionInstance.RemoveFirst(); + if (partitionInstance.Count > 0) + { + var firstItem = partitionInstance.First!.Value; + partitionInstance.RemoveFirst(); - return firstItem; - } + return firstItem; } - - return null; } - public void EnqueueItem(MqttPacketBusItem item, MqttPacketBusPartition partition) - { - ArgumentNullException.ThrowIfNull(item); + return null; + } - lock (_syncRoot) - { - _partitions[(int)partition].AddLast(item); - _signal.Set(); - } + public void EnqueueItem(MqttPacketBusItem item, MqttPacketBusPartition partition) + { + ArgumentNullException.ThrowIfNull(item); + + lock (_syncRoot) + { + _partitions[(int)partition].AddLast(item); + _signal.Set(); } + } - public List ExportPackets(MqttPacketBusPartition partition) + public List ExportPackets(MqttPacketBusPartition partition) + { + lock (_syncRoot) { - lock (_syncRoot) - { - return _partitions[(int)partition].Select(i => i.Packet).ToList(); - } + return _partitions[(int)partition].Select(i => i.Packet).ToList(); } + } - public int PartitionItemsCount(MqttPacketBusPartition partition) + public int PartitionItemsCount(MqttPacketBusPartition partition) + { + lock (_syncRoot) { - lock (_syncRoot) - { - return _partitions[(int)partition].Count; - } + return _partitions[(int)partition].Count; } + } - void MoveActivePartition() + void MoveActivePartition() + { + if (_activePartition >= _partitions.Length - 1) { - if (_activePartition >= _partitions.Length - 1) - { - _activePartition = 0; - } - else - { - _activePartition++; - } + _activePartition = 0; + } + else + { + _activePartition++; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/MqttPacketBusItem.cs b/Source/MQTTnet/Internal/MqttPacketBusItem.cs index 6fb5b114d..df02f726b 100644 --- a/Source/MQTTnet/Internal/MqttPacketBusItem.cs +++ b/Source/MQTTnet/Internal/MqttPacketBusItem.cs @@ -6,40 +6,39 @@ using System.Threading.Tasks; using MQTTnet.Packets; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public sealed class MqttPacketBusItem { - public sealed class MqttPacketBusItem - { - readonly AsyncTaskCompletionSource _promise = new AsyncTaskCompletionSource(); + readonly AsyncTaskCompletionSource _promise = new(); - public MqttPacketBusItem(MqttPacket packet) - { - Packet = packet ?? throw new ArgumentNullException(nameof(packet)); - } + public MqttPacketBusItem(MqttPacket packet) + { + Packet = packet ?? throw new ArgumentNullException(nameof(packet)); + } - public event EventHandler Completed; + public event EventHandler Completed; - public MqttPacket Packet { get; } + public MqttPacket Packet { get; } - public void Cancel() - { - _promise.TrySetCanceled(); - } + public void Cancel() + { + _promise.TrySetCanceled(); + } - public void Complete() - { - _promise.TrySetResult(Packet); - Completed?.Invoke(this, EventArgs.Empty); - } + public void Complete() + { + _promise.TrySetResult(Packet); + Completed?.Invoke(this, EventArgs.Empty); + } - public void Fail(Exception exception) - { - _promise.TrySetException(exception); - } + public void Fail(Exception exception) + { + _promise.TrySetException(exception); + } - public Task WaitAsync() - { - return _promise.Task; - } + public Task WaitAsync() + { + return _promise.Task; } } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/MqttPacketBusPartition.cs b/Source/MQTTnet/Internal/MqttPacketBusPartition.cs index d8700399d..f7f241e2b 100644 --- a/Source/MQTTnet/Internal/MqttPacketBusPartition.cs +++ b/Source/MQTTnet/Internal/MqttPacketBusPartition.cs @@ -2,14 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public enum MqttPacketBusPartition { - public enum MqttPacketBusPartition - { - Data, - - Control, - - Health - } + Data, + + Control, + + Health } \ No newline at end of file diff --git a/Source/MQTTnet/Internal/TaskExtensions.cs b/Source/MQTTnet/Internal/TaskExtensions.cs index 5ab14f75e..753a64460 100644 --- a/Source/MQTTnet/Internal/TaskExtensions.cs +++ b/Source/MQTTnet/Internal/TaskExtensions.cs @@ -6,53 +6,52 @@ using System.Threading.Tasks; using MQTTnet.Diagnostics.Logger; -namespace MQTTnet.Internal +namespace MQTTnet.Internal; + +public static class TaskExtensions { - public static class TaskExtensions + public static void RunInBackground(this Task task, MqttNetSourceLogger logger = null) + { + task?.ContinueWith( + t => + { + // Consume the exception first so that we get no exception regarding the not observed exception. + var exception = t.Exception; + logger?.Error(exception, "Unhandled exception in background task."); + }, + TaskContinuationOptions.OnlyOnFaulted); + } + + public static async Task WaitAsync(this Task task, Task sender, MqttNetSourceLogger logger) { - public static void RunInBackground(this Task task, MqttNetSourceLogger logger = null) + ArgumentNullException.ThrowIfNull(logger); + + if (task == null) { - task?.ContinueWith( - t => - { - // Consume the exception first so that we get no exception regarding the not observed exception. - var exception = t.Exception; - logger?.Error(exception, "Unhandled exception in background task."); - }, - TaskContinuationOptions.OnlyOnFaulted); + return; } - public static async Task WaitAsync(this Task task, Task sender, MqttNetSourceLogger logger) + if (task == sender) { - ArgumentNullException.ThrowIfNull(logger); - - if (task == null) + // 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; } - 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, "Error while waiting for background task."); - 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, "Error while waiting for background task."); + return; + } - try - { - await task.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } + try + { + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { } } } \ No newline at end of file diff --git a/Source/MQTTnet/LowLevelClient/ILowLevelMqttClient.cs b/Source/MQTTnet/LowLevelClient/ILowLevelMqttClient.cs index e6d7620b6..bdd3687f5 100644 --- a/Source/MQTTnet/LowLevelClient/ILowLevelMqttClient.cs +++ b/Source/MQTTnet/LowLevelClient/ILowLevelMqttClient.cs @@ -4,20 +4,19 @@ using MQTTnet.Diagnostics.PacketInspection; using MQTTnet.Packets; -namespace MQTTnet.LowLevelClient +namespace MQTTnet.LowLevelClient; + +public interface ILowLevelMqttClient : IDisposable { - public interface ILowLevelMqttClient : IDisposable - { - event Func InspectPacketAsync; + event Func InspectPacketAsync; - bool IsConnected { get; } + bool IsConnected { get; } - Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default); + Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default); - Task DisconnectAsync(CancellationToken cancellationToken = default); + Task DisconnectAsync(CancellationToken cancellationToken = default); - Task ReceiveAsync(CancellationToken cancellationToken = default); + Task ReceiveAsync(CancellationToken cancellationToken = default); - Task SendAsync(MqttPacket packet, CancellationToken cancellationToken = default); - } + Task SendAsync(MqttPacket packet, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/MQTTnet/LowLevelClient/LowLevelMqttClient.cs b/Source/MQTTnet/LowLevelClient/LowLevelMqttClient.cs index 186e8bfd1..eb839d8a8 100644 --- a/Source/MQTTnet/LowLevelClient/LowLevelMqttClient.cs +++ b/Source/MQTTnet/LowLevelClient/LowLevelMqttClient.cs @@ -12,135 +12,134 @@ using MQTTnet.Internal; using MQTTnet.Packets; -namespace MQTTnet.LowLevelClient +namespace MQTTnet.LowLevelClient; + +public sealed class LowLevelMqttClient : ILowLevelMqttClient { - public sealed class LowLevelMqttClient : ILowLevelMqttClient + readonly IMqttClientAdapterFactory _clientAdapterFactory; + readonly AsyncEvent _inspectPacketEvent = new(); + readonly MqttNetSourceLogger _logger; + readonly IMqttNetLogger _rootLogger; + + IMqttChannelAdapter _adapter; + + public LowLevelMqttClient(IMqttClientAdapterFactory clientAdapterFactory, IMqttNetLogger logger) { - readonly IMqttClientAdapterFactory _clientAdapterFactory; - readonly AsyncEvent _inspectPacketEvent = new AsyncEvent(); - readonly MqttNetSourceLogger _logger; - readonly IMqttNetLogger _rootLogger; + _clientAdapterFactory = clientAdapterFactory ?? throw new ArgumentNullException(nameof(clientAdapterFactory)); + + _rootLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger.WithSource(nameof(LowLevelMqttClient)); + } - IMqttChannelAdapter _adapter; + public event Func InspectPacketAsync + { + add => _inspectPacketEvent.AddHandler(value); + remove => _inspectPacketEvent.RemoveHandler(value); + } - public LowLevelMqttClient(IMqttClientAdapterFactory clientAdapterFactory, IMqttNetLogger logger) - { - _clientAdapterFactory = clientAdapterFactory ?? throw new ArgumentNullException(nameof(clientAdapterFactory)); + public bool IsConnected => _adapter != null; - _rootLogger = logger ?? throw new ArgumentNullException(nameof(logger)); - _logger = logger.WithSource(nameof(LowLevelMqttClient)); + public async Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(options); + + if (_adapter != null) + { + throw new InvalidOperationException("Low level MQTT client is already connected. Disconnect first before connecting again."); } - public event Func InspectPacketAsync + MqttPacketInspector packetInspector = null; + if (_inspectPacketEvent.HasHandlers) { - add => _inspectPacketEvent.AddHandler(value); - remove => _inspectPacketEvent.RemoveHandler(value); + packetInspector = new MqttPacketInspector(_inspectPacketEvent, _rootLogger); } - public bool IsConnected => _adapter != null; + var newAdapter = _clientAdapterFactory.CreateClientAdapter(options, packetInspector, _rootLogger); - public async Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken) + try + { + _logger.Verbose("Trying to connect with server '{0}'", options.ChannelOptions); + await newAdapter.ConnectAsync(cancellationToken).ConfigureAwait(false); + _logger.Verbose("Connection with server established"); + } + catch { - ArgumentNullException.ThrowIfNull(options); + _adapter?.Dispose(); + throw; + } - if (_adapter != null) - { - throw new InvalidOperationException("Low level MQTT client is already connected. Disconnect first before connecting again."); - } + _adapter = newAdapter; + } - MqttPacketInspector packetInspector = null; - if (_inspectPacketEvent.HasHandlers) - { - packetInspector = new MqttPacketInspector(_inspectPacketEvent, _rootLogger); - } + public async Task DisconnectAsync(CancellationToken cancellationToken) + { + var adapter = _adapter; + if (adapter == null) + { + throw new InvalidOperationException("Low level MQTT client is not connected."); + } - var newAdapter = _clientAdapterFactory.CreateClientAdapter(options, packetInspector, _rootLogger); + try + { + await adapter.DisconnectAsync(cancellationToken).ConfigureAwait(false); + } + catch + { + Dispose(); + throw; + } + } - try - { - _logger.Verbose("Trying to connect with server '{0}'", options.ChannelOptions); - await newAdapter.ConnectAsync(cancellationToken).ConfigureAwait(false); - _logger.Verbose("Connection with server established"); - } - catch (Exception) - { - _adapter?.Dispose(); - throw; - } + public void Dispose() + { + _adapter?.Dispose(); + _adapter = null; + } - _adapter = newAdapter; + public async Task ReceiveAsync(CancellationToken cancellationToken) + { + var adapter = _adapter; + if (adapter == null) + { + throw new InvalidOperationException("Low level MQTT client is not connected."); } - public async Task DisconnectAsync(CancellationToken cancellationToken) + try { - var adapter = _adapter; - if (adapter == null) + var receivedPacket = await adapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); + if (receivedPacket == null) { - throw new InvalidOperationException("Low level MQTT client is not connected."); + // Graceful socket close. + throw new MqttCommunicationException("The connection is closed."); } - try - { - await adapter.DisconnectAsync(cancellationToken).ConfigureAwait(false); - } - catch - { - Dispose(); - throw; - } + return receivedPacket; } - - public void Dispose() + catch { - _adapter?.Dispose(); - _adapter = null; + Dispose(); + throw; } + } - public async Task ReceiveAsync(CancellationToken cancellationToken) - { - var adapter = _adapter; - if (adapter == null) - { - throw new InvalidOperationException("Low level MQTT client is not connected."); - } + public async Task SendAsync(MqttPacket packet, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(packet); - try - { - var receivedPacket = await adapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); - if (receivedPacket == null) - { - // Graceful socket close. - throw new MqttCommunicationException("The connection is closed."); - } - - return receivedPacket; - } - catch - { - Dispose(); - throw; - } + var adapter = _adapter; + if (adapter == null) + { + throw new InvalidOperationException("Low level MQTT client is not connected."); } - public async Task SendAsync(MqttPacket packet, CancellationToken cancellationToken) + try { - ArgumentNullException.ThrowIfNull(packet); - - var adapter = _adapter; - if (adapter == null) - { - throw new InvalidOperationException("Low level MQTT client is not connected."); - } - - try - { - await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); - } - catch - { - Dispose(); - throw; - } + await adapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); + } + catch + { + Dispose(); + throw; } } } \ No newline at end of file diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 1b1f39879..380beef9e 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -31,7 +31,7 @@ git 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 Blazor en-US - false + true false nuget.png true diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index eb402e155..ca7264f9d 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -9,138 +9,137 @@ using System.Buffers; using System.Collections.Generic; -namespace MQTTnet +namespace MQTTnet; + +public sealed class MqttApplicationMessage { - public sealed class MqttApplicationMessage + /// + /// Gets or sets the content type. + /// The content type must be a UTF-8 encoded string. The content type value identifies the kind of UTF-8 encoded + /// payload. + /// + public string ContentType { get; set; } + + /// + /// Gets or sets the correlation data. + /// In order for the sender to know what sent message the response refers to it can also send correlation data with the + /// published message. + /// Hint: MQTT 5 feature only. + /// + public byte[] CorrelationData { get; set; } + + /// + /// If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or Server has attempted + /// to send this MQTT PUBLISH Packet. + /// If the DUP flag is set to 1, it indicates that this might be re-delivery of an earlier attempt to send the Packet. + /// The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH Packet + /// [MQTT-3.3.1.-1]. + /// The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2]. + /// + public bool Dup { get; set; } + + /// + /// Gets or sets the message expiry interval. + /// A client can set the message expiry interval in seconds for each PUBLISH message individually. + /// This interval defines the period of time that the broker stores the PUBLISH message for any matching subscribers + /// that are not currently connected. + /// When no message expiry interval is set, the broker must store the message for matching subscribers indefinitely. + /// When the retained=true option is set on the PUBLISH message, this interval also defines how long a message is + /// retained on a topic. + /// Hint: MQTT 5 feature only. + /// + public uint MessageExpiryInterval { get; set; } + + /// + /// Set an ArraySegment as Payload. + /// + public ArraySegment PayloadSegment { - /// - /// Gets or sets the content type. - /// The content type must be a UTF-8 encoded string. The content type value identifies the kind of UTF-8 encoded - /// payload. - /// - public string ContentType { get; set; } - - /// - /// Gets or sets the correlation data. - /// In order for the sender to know what sent message the response refers to it can also send correlation data with the - /// published message. - /// Hint: MQTT 5 feature only. - /// - public byte[] CorrelationData { get; set; } - - /// - /// If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or Server has attempted - /// to send this MQTT PUBLISH Packet. - /// If the DUP flag is set to 1, it indicates that this might be re-delivery of an earlier attempt to send the Packet. - /// The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH Packet - /// [MQTT-3.3.1.-1]. - /// The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2]. - /// - public bool Dup { get; set; } - - /// - /// Gets or sets the message expiry interval. - /// A client can set the message expiry interval in seconds for each PUBLISH message individually. - /// This interval defines the period of time that the broker stores the PUBLISH message for any matching subscribers - /// that are not currently connected. - /// When no message expiry interval is set, the broker must store the message for matching subscribers indefinitely. - /// When the retained=true option is set on the PUBLISH message, this interval also defines how long a message is - /// retained on a topic. - /// Hint: MQTT 5 feature only. - /// - public uint MessageExpiryInterval { get; set; } - - /// - /// Set an ArraySegment as Payload. - /// - public ArraySegment PayloadSegment - { - set { Payload = new ReadOnlySequence(value); } - } - - /// - /// Get or set ReadOnlySequence style of Payload. - /// This payload type is used internally and is recommended for public use. - /// It can be used in combination with a RecyclableMemoryStream to publish - /// large buffered messages without allocating large chunks of memory. - /// - public ReadOnlySequence Payload { get; set; } = EmptyBuffer.ReadOnlySequence; - - /// - /// Gets or sets the payload format indicator. - /// The payload format indicator is part of any MQTT packet that can contain a payload. The indicator is an optional - /// byte value. - /// A value of 0 indicates an “unspecified byte stream”. - /// A value of 1 indicates a "UTF-8 encoded payload". - /// If no payload format indicator is provided, the default value is 0. - /// Hint: MQTT 5 feature only. - /// - public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - - /// - /// Gets or sets the quality of service level. - /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message - /// that defines the guarantee of delivery for a specific message. - /// There are 3 QoS levels in MQTT: - /// - At most once (0): Message gets delivered no time, once or multiple times. - /// - At least once (1): Message gets delivered at least once (one time or more often). - /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). - /// - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } - - /// - /// Gets or sets the response topic. - /// In MQTT 5 the ability to publish a response topic was added in the publish message which allows you to implement - /// the request/response pattern between clients that is common in web applications. - /// Hint: MQTT 5 feature only. - /// - public string ResponseTopic { get; set; } - - /// - /// Gets or sets a value indicating whether the message should be retained or not. - /// A retained message is a normal MQTT message with the retained flag set to true. - /// The broker stores the last retained message and the corresponding QoS for that topic. - /// - public bool Retain { get; set; } - - /// - /// Gets or sets the subscription identifiers. - /// The client can specify a subscription identifier when subscribing. - /// The broker will establish and store the mapping relationship between this subscription and subscription identifier - /// when successfully create or modify subscription. - /// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to - /// the client when need to forward PUBLISH packets matching this subscription to this client. - /// Hint: MQTT 5 feature only. - /// - public List SubscriptionIdentifiers { get; set; } - - /// - /// Gets or sets the MQTT topic. - /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected - /// client. - /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level - /// separator). - /// - public string Topic { get; set; } - - /// - /// Gets or sets the topic alias. - /// Topic aliases were introduced are a mechanism for reducing the size of published packets by reducing the size of - /// the topic field. - /// A value of 0 indicates no topic alias is used. - /// Hint: MQTT 5 feature only. - /// - public ushort TopicAlias { get; set; } - - /// - /// Gets or sets the user properties. - /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT - /// packet. - /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add - /// metadata to MQTT messages and pass information between publisher, broker, and subscriber. - /// The feature is very similar to the HTTP header concept. - /// Hint: MQTT 5 feature only. - /// - public List UserProperties { get; set; } + set => Payload = new ReadOnlySequence(value); } + + /// + /// Get or set ReadOnlySequence style of Payload. + /// This payload type is used internally and is recommended for public use. + /// It can be used in combination with a RecyclableMemoryStream to publish + /// large buffered messages without allocating large chunks of memory. + /// + public ReadOnlySequence Payload { get; set; } = EmptyBuffer.ReadOnlySequence; + + /// + /// Gets or sets the payload format indicator. + /// The payload format indicator is part of any MQTT packet that can contain a payload. The indicator is an optional + /// byte value. + /// A value of 0 indicates an “unspecified byte stream”. + /// A value of 1 indicates a "UTF-8 encoded payload". + /// If no payload format indicator is provided, the default value is 0. + /// Hint: MQTT 5 feature only. + /// + public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; + + /// + /// Gets or sets the quality of service level. + /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message + /// that defines the guarantee of delivery for a specific message. + /// There are 3 QoS levels in MQTT: + /// - At most once (0): Message gets delivered no time, once or multiple times. + /// - At least once (1): Message gets delivered at least once (one time or more often). + /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). + /// + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + + /// + /// Gets or sets the response topic. + /// In MQTT 5 the ability to publish a response topic was added in the publish message which allows you to implement + /// the request/response pattern between clients that is common in web applications. + /// Hint: MQTT 5 feature only. + /// + public string ResponseTopic { get; set; } + + /// + /// Gets or sets a value indicating whether the message should be retained or not. + /// A retained message is a normal MQTT message with the retained flag set to true. + /// The broker stores the last retained message and the corresponding QoS for that topic. + /// + public bool Retain { get; set; } + + /// + /// Gets or sets the subscription identifiers. + /// The client can specify a subscription identifier when subscribing. + /// The broker will establish and store the mapping relationship between this subscription and subscription identifier + /// when successfully create or modify subscription. + /// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to + /// the client when need to forward PUBLISH packets matching this subscription to this client. + /// Hint: MQTT 5 feature only. + /// + public List SubscriptionIdentifiers { get; set; } + + /// + /// Gets or sets the MQTT topic. + /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected + /// client. + /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level + /// separator). + /// + public string Topic { get; set; } + + /// + /// Gets or sets the topic alias. + /// Topic aliases were introduced are a mechanism for reducing the size of published packets by reducing the size of + /// the topic field. + /// A value of 0 indicates no topic alias is used. + /// Hint: MQTT 5 feature only. + /// + public ushort TopicAlias { get; set; } + + /// + /// Gets or sets the user properties. + /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT + /// packet. + /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add + /// metadata to MQTT messages and pass information between publisher, broker, and subscriber. + /// The feature is very similar to the HTTP header concept. + /// Hint: MQTT 5 feature only. + /// + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index 2c5c2dd70..08a2a4a7b 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -14,265 +14,260 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet +namespace MQTTnet; + +public sealed class MqttApplicationMessageBuilder { - public sealed class MqttApplicationMessageBuilder + string _contentType; + byte[] _correlationData; + uint _messageExpiryInterval; + + MqttPayloadFormatIndicator _payloadFormatIndicator; + ReadOnlySequence _payload; + MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + string _responseTopic; + bool _retain; + List _subscriptionIdentifiers; + string _topic; + ushort _topicAlias; + List _userProperties; + + public MqttApplicationMessage Build() { - string _contentType; - byte[] _correlationData; - uint _messageExpiryInterval; - - MqttPayloadFormatIndicator _payloadFormatIndicator; - ReadOnlySequence _payload; - MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; - string _responseTopic; - bool _retain; - List _subscriptionIdentifiers; - string _topic; - ushort _topicAlias; - List _userProperties; - - public MqttApplicationMessage Build() - { - if (_topicAlias == 0 && string.IsNullOrEmpty(_topic)) - { - throw new MqttProtocolViolationException("Topic or TopicAlias is not set."); - } - - var applicationMessage = new MqttApplicationMessage - { - Topic = _topic, - Payload = _payload, - QualityOfServiceLevel = _qualityOfServiceLevel, - Retain = _retain, - ContentType = _contentType, - ResponseTopic = _responseTopic, - CorrelationData = _correlationData, - TopicAlias = _topicAlias, - SubscriptionIdentifiers = _subscriptionIdentifiers, - MessageExpiryInterval = _messageExpiryInterval, - PayloadFormatIndicator = _payloadFormatIndicator, - UserProperties = _userProperties - }; - - return applicationMessage; - } - - /// - /// Adds the content type to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithContentType(string contentType) - { - _contentType = contentType; - return this; - } - - /// - /// Adds the correlation data to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithCorrelationData(byte[] correlationData) + if (_topicAlias == 0 && string.IsNullOrEmpty(_topic)) { - _correlationData = correlationData; - return this; + throw new MqttProtocolViolationException("Topic or TopicAlias is not set."); } - /// - /// Adds the message expiry interval in seconds to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpiryInterval) + var applicationMessage = new MqttApplicationMessage { - _messageExpiryInterval = messageExpiryInterval; - return this; - } + Topic = _topic, + Payload = _payload, + QualityOfServiceLevel = _qualityOfServiceLevel, + Retain = _retain, + ContentType = _contentType, + ResponseTopic = _responseTopic, + CorrelationData = _correlationData, + TopicAlias = _topicAlias, + SubscriptionIdentifiers = _subscriptionIdentifiers, + MessageExpiryInterval = _messageExpiryInterval, + PayloadFormatIndicator = _payloadFormatIndicator, + UserProperties = _userProperties + }; + + return applicationMessage; + } - public MqttApplicationMessageBuilder WithPayload(byte[] payload) - { - _payload = payload == null || payload.Length == 0 ? EmptyBuffer.ReadOnlySequence : new ReadOnlySequence(payload); - return this; - } + /// + /// Adds the content type to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithContentType(string contentType) + { + _contentType = contentType; + return this; + } - public MqttApplicationMessageBuilder WithPayload(ArraySegment payloadSegment) - { - _payload = new ReadOnlySequence(payloadSegment); - return this; - } + /// + /// Adds the correlation data to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithCorrelationData(byte[] correlationData) + { + _correlationData = correlationData; + return this; + } - public MqttApplicationMessageBuilder WithPayload(IEnumerable payload) - { - if (payload == null) - { - return WithPayload(default(byte[])); - } + /// + /// Adds the message expiry interval in seconds to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpiryInterval) + { + _messageExpiryInterval = messageExpiryInterval; + return this; + } - if (payload is byte[] byteArray) - { - return WithPayload(byteArray); - } + public MqttApplicationMessageBuilder WithPayload(byte[] payload) + { + _payload = payload == null || payload.Length == 0 ? EmptyBuffer.ReadOnlySequence : new ReadOnlySequence(payload); + return this; + } - if (payload is ArraySegment arraySegment) - { - return WithPayloadSegment(arraySegment); - } + public MqttApplicationMessageBuilder WithPayload(ArraySegment payloadSegment) + { + _payload = new ReadOnlySequence(payloadSegment); + return this; + } - return WithPayload(payload.ToArray()); + public MqttApplicationMessageBuilder WithPayload(IEnumerable payload) + { + if (payload == null) + { + return WithPayload(default(byte[])); } - public MqttApplicationMessageBuilder WithPayload(Stream payload) + if (payload is byte[] byteArray) { - return payload == null ? WithPayload(default(byte[])) : WithPayload(payload, payload.Length - payload.Position); + return WithPayload(byteArray); } - public MqttApplicationMessageBuilder WithPayload(Stream payload, long length) + if (payload is ArraySegment arraySegment) { - if (payload == null || length == 0) - { - return WithPayload(default(byte[])); - } + return WithPayloadSegment(arraySegment); + } - var payloadBuffer = new byte[length]; - var totalRead = 0; - do - { - var bytesRead = payload.Read(payloadBuffer, totalRead, payloadBuffer.Length - totalRead); - if (bytesRead == 0) - { - break; - } + return WithPayload(payload.ToArray()); + } - totalRead += bytesRead; - } while (totalRead < length); + public MqttApplicationMessageBuilder WithPayload(Stream payload) + { + return payload == null ? WithPayload(default(byte[])) : WithPayload(payload, payload.Length - payload.Position); + } - return WithPayload(payloadBuffer); + public MqttApplicationMessageBuilder WithPayload(Stream payload, long length) + { + if (payload == null || length == 0) + { + return WithPayload(default(byte[])); } - public MqttApplicationMessageBuilder WithPayload(string payload) + var payloadBuffer = new byte[length]; + var totalRead = 0; + do { - if (string.IsNullOrEmpty(payload)) + var bytesRead = payload.Read(payloadBuffer, totalRead, payloadBuffer.Length - totalRead); + if (bytesRead == 0) { - return WithPayload(default(byte[])); + break; } - var payloadBuffer = Encoding.UTF8.GetBytes(payload); - return WithPayload(payloadBuffer); - } + totalRead += bytesRead; + } while (totalRead < length); - public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payload) - { - _payload = payload; - return this; - } + return WithPayload(payloadBuffer); + } - /// - /// Adds the payload format indicator to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadFormatIndicator payloadFormatIndicator) + public MqttApplicationMessageBuilder WithPayload(string payload) + { + if (string.IsNullOrEmpty(payload)) { - _payloadFormatIndicator = payloadFormatIndicator; - return this; + return WithPayload(default(byte[])); } - public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment payloadSegment) - { - _payload = new ReadOnlySequence(payloadSegment); - return this; - } + var payloadBuffer = Encoding.UTF8.GetBytes(payload); + return WithPayload(payloadBuffer); + } - public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory payloadSegment) - { - return MemoryMarshal.TryGetArray(payloadSegment, out var segment) ? WithPayloadSegment(segment) : WithPayload(payloadSegment.ToArray()); - } + public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payload) + { + _payload = payload; + return this; + } - /// - /// The quality of service level. - /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message - /// that defines the guarantee of delivery for a specific message. - /// There are 3 QoS levels in MQTT: - /// - At most once (0): Message gets delivered no time, once or multiple times. - /// - At least once (1): Message gets delivered at least once (one time or more often). - /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). - /// - public MqttApplicationMessageBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel) - { - _qualityOfServiceLevel = qualityOfServiceLevel; - return this; - } + /// + /// Adds the payload format indicator to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadFormatIndicator payloadFormatIndicator) + { + _payloadFormatIndicator = payloadFormatIndicator; + return this; + } - /// - /// Adds the response topic to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithResponseTopic(string responseTopic) - { - _responseTopic = responseTopic; - return this; - } + public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment payloadSegment) + { + _payload = new ReadOnlySequence(payloadSegment); + return this; + } - /// - /// A value indicating whether the message should be retained or not. - /// A retained message is a normal MQTT message with the retained flag set to true. - /// The broker stores the last retained message and the corresponding QoS for that topic. - /// - public MqttApplicationMessageBuilder WithRetainFlag(bool value = true) - { - _retain = value; - return this; - } + public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory payloadSegment) + { + return MemoryMarshal.TryGetArray(payloadSegment, out var segment) ? WithPayloadSegment(segment) : WithPayload(payloadSegment.ToArray()); + } - /// - /// Adds the subscription identifier to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) - { - if (_subscriptionIdentifiers == null) - { - _subscriptionIdentifiers = new List(); - } + /// + /// The quality of service level. + /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message + /// that defines the guarantee of delivery for a specific message. + /// There are 3 QoS levels in MQTT: + /// - At most once (0): Message gets delivered no time, once or multiple times. + /// - At least once (1): Message gets delivered at least once (one time or more often). + /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). + /// + public MqttApplicationMessageBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel) + { + _qualityOfServiceLevel = qualityOfServiceLevel; + return this; + } - _subscriptionIdentifiers.Add(subscriptionIdentifier); - return this; - } + /// + /// Adds the response topic to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithResponseTopic(string responseTopic) + { + _responseTopic = responseTopic; + return this; + } - /// - /// The MQTT topic. - /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected - /// client. - /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level - /// separator). - /// - public MqttApplicationMessageBuilder WithTopic(string topic) - { - _topic = topic; - return this; - } + /// + /// A value indicating whether the message should be retained or not. + /// A retained message is a normal MQTT message with the retained flag set to true. + /// The broker stores the last retained message and the corresponding QoS for that topic. + /// + public MqttApplicationMessageBuilder WithRetainFlag(bool value = true) + { + _retain = value; + return this; + } - /// - /// Adds the topic alias to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithTopicAlias(ushort topicAlias) + /// + /// Adds the subscription identifier to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) + { + if (_subscriptionIdentifiers == null) { - _topicAlias = topicAlias; - return this; + _subscriptionIdentifiers = new List(); } - /// - /// Adds the user property to the message. - /// MQTT 5.0.0+ feature. - /// - public MqttApplicationMessageBuilder WithUserProperty(string name, string value) - { - if (_userProperties == null) - { - _userProperties = new List(); - } + _subscriptionIdentifiers.Add(subscriptionIdentifier); + return this; + } - _userProperties.Add(new MqttUserProperty(name, value)); - return this; - } + /// + /// The MQTT topic. + /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected + /// client. + /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level + /// separator). + /// + public MqttApplicationMessageBuilder WithTopic(string topic) + { + _topic = topic; + return this; + } + + /// + /// Adds the topic alias to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithTopicAlias(ushort topicAlias) + { + _topicAlias = topicAlias; + return this; + } + + /// + /// Adds the user property to the message. + /// MQTT 5.0.0+ feature. + /// + public MqttApplicationMessageBuilder WithUserProperty(string name, string value) + { + _userProperties ??= []; + _userProperties.Add(new MqttUserProperty(name, value)); + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet/MqttApplicationMessageValidator.cs b/Source/MQTTnet/MqttApplicationMessageValidator.cs index d7eb3b611..e1ed2ffe7 100644 --- a/Source/MQTTnet/MqttApplicationMessageValidator.cs +++ b/Source/MQTTnet/MqttApplicationMessageValidator.cs @@ -3,63 +3,61 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using MQTTnet.Formatter; using MQTTnet.Protocol; -namespace MQTTnet +namespace MQTTnet; + +public static class MqttApplicationMessageValidator { - public static class MqttApplicationMessageValidator + public static void ThrowIfNotSupported(MqttApplicationMessage applicationMessage, MqttProtocolVersion protocolVersion) { - public static void ThrowIfNotSupported(MqttApplicationMessage applicationMessage, MqttProtocolVersion protocolVersion) - { - ArgumentNullException.ThrowIfNull(applicationMessage); - - if (protocolVersion == MqttProtocolVersion.V500) - { - // Everything is supported. - return; - } + ArgumentNullException.ThrowIfNull(applicationMessage); - if (applicationMessage.ContentType?.Any() == true) - { - Throw(nameof(applicationMessage.ContentType)); - } + if (protocolVersion == MqttProtocolVersion.V500) + { + // Everything is supported. + return; + } - if (applicationMessage.UserProperties?.Any() == true) - { - Throw(nameof(applicationMessage.UserProperties)); - } + if (applicationMessage.ContentType?.Length > 0) + { + Throw(nameof(applicationMessage.ContentType)); + } - if (applicationMessage.CorrelationData?.Any() == true) - { - Throw(nameof(applicationMessage.CorrelationData)); - } + if (applicationMessage.UserProperties?.Count > 0) + { + Throw(nameof(applicationMessage.UserProperties)); + } - if (applicationMessage.ResponseTopic?.Any() == true) - { - Throw(nameof(applicationMessage.ResponseTopic)); - } + if (applicationMessage.CorrelationData?.Length > 0) + { + Throw(nameof(applicationMessage.CorrelationData)); + } - if (applicationMessage.SubscriptionIdentifiers?.Any() == true) - { - Throw(nameof(applicationMessage.SubscriptionIdentifiers)); - } + if (applicationMessage.ResponseTopic?.Length > 0) + { + Throw(nameof(applicationMessage.ResponseTopic)); + } - if (applicationMessage.TopicAlias > 0) - { - Throw(nameof(applicationMessage.TopicAlias)); - } + if (applicationMessage.SubscriptionIdentifiers?.Count > 0) + { + Throw(nameof(applicationMessage.SubscriptionIdentifiers)); + } - if (applicationMessage.PayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified) - { - Throw(nameof(applicationMessage.PayloadFormatIndicator)); - } + if (applicationMessage.TopicAlias > 0) + { + Throw(nameof(applicationMessage.TopicAlias)); } - static void Throw(string featureName) + if (applicationMessage.PayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified) { - throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0."); + Throw(nameof(applicationMessage.PayloadFormatIndicator)); } } + + static void Throw(string featureName) + { + throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0."); + } } \ No newline at end of file diff --git a/Source/MQTTnet/MqttClient.cs b/Source/MQTTnet/MqttClient.cs index 9dbe960ed..8f278b857 100644 --- a/Source/MQTTnet/MqttClient.cs +++ b/Source/MQTTnet/MqttClient.cs @@ -134,10 +134,8 @@ public async Task ConnectAsync(MqttClientOptions option { // Fall back to the general timeout specified in the options if the user passed // CancellationToken.None or similar. - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(Options.Timeout); + connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false); } if (connectResult.ResultCode != MqttClientConnectResultCode.Success) @@ -219,10 +217,8 @@ public async Task DisconnectAsync(MqttClientDisconnectOptions options, Cancellat } else { - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - await Send(disconnectPacket, timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(Options.Timeout); + await Send(disconnectPacket, timeout.Token).ConfigureAwait(false); } } finally @@ -244,10 +240,8 @@ public async Task PingAsync(CancellationToken cancellationToken = default) } else { - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - await Request(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(Options.Timeout); + await Request(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false); } } @@ -335,13 +329,11 @@ public async Task SubscribeAsync(MqttClientSubscribeO } else { - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - subAckPacket = await Request(subscribePacket, timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(Options.Timeout); + subAckPacket = await Request(subscribePacket, timeout.Token).ConfigureAwait(false); } - return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket); + return MqttClientSubscribeResultFactory.Create(subscribePacket, subAckPacket); } public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default) @@ -371,13 +363,11 @@ public async Task UnsubscribeAsync(MqttClientUnsubs } else { - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - unsubAckPacket = await Request(unsubscribePacket, timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(Options.Timeout); + unsubAckPacket = await Request(unsubscribePacket, timeout.Token).ConfigureAwait(false); } - return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket); + return MqttClientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket); } protected override void Dispose(bool disposing) @@ -451,7 +441,7 @@ async Task Authenticate(IMqttChannelAdapter channelAdap if (receivedPacket is MqttConnAckPacket connAckPacket) { - result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion); + result = MqttClientConnectResultFactory.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion); break; } @@ -473,10 +463,10 @@ async Task Authenticate(IMqttChannelAdapter channelAdap return result; } - async Task HandleEnhancedAuthentication(MqttAuthPacket authPacket, CancellationToken cancellationToken) + Task HandleEnhancedAuthentication(MqttAuthPacket authPacket, CancellationToken cancellationToken) { var eventArgs = new MqttEnhancedAuthenticationEventArgs(authPacket, _adapter, cancellationToken); - await Options.EnhancedAuthenticationHandler.HandleEnhancedAuthenticationAsync(eventArgs); + return Options.EnhancedAuthenticationHandler.HandleEnhancedAuthenticationAsync(eventArgs); } void Cleanup() @@ -510,24 +500,24 @@ async Task ConnectInternal(IMqttChannelAdapter channelA { var backgroundCancellationToken = _mqttClientAlive.Token; - using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) - { - _logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions); - await channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false); - _logger.Verbose("Connection with server established"); + using var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken); + _logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions); + await channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false); + _logger.Verbose("Connection with server established"); - _publishPacketReceiverQueue?.Dispose(); - _publishPacketReceiverQueue = new AsyncQueue(); - - var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false); - if (connectResult.ResultCode == MqttClientConnectResultCode.Success) - { - _publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken); - _packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken); - } + _publishPacketReceiverQueue?.Dispose(); + _publishPacketReceiverQueue = new AsyncQueue(); + var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false); + if (connectResult.ResultCode != MqttClientConnectResultCode.Success) + { return connectResult; } + + _publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken); + _packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken); + + return connectResult; } async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) @@ -540,10 +530,8 @@ async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectRes { _logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout); - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); - } + using var timeout = new CancellationTokenSource(Options.Timeout); + await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); } _logger.Verbose("Disconnected from adapter."); @@ -747,7 +735,7 @@ async Task PublishAtLeastOnce(MqttPublishPacket publish publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); var pubAckPacket = await Request(publishPacket, cancellationToken).ConfigureAwait(false); - return MqttClientResultFactory.PublishResult.Create(pubAckPacket); + return MqttClientPublishResultFactory.Create(pubAckPacket); } async Task PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) @@ -757,7 +745,7 @@ async Task PublishAtMostOnce(MqttPublishPacket publishP // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] await Send(publishPacket, cancellationToken).ConfigureAwait(false); - return MqttClientResultFactory.PublishResult.Create(null); + return MqttClientPublishResultFactory.Create(null); } catch (Exception exception) { @@ -785,7 +773,7 @@ async Task PublishExactlyOnce(MqttPublishPacket publish var pubCompPacket = await Request(pubRelPacket, cancellationToken).ConfigureAwait(false); - return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket); + return MqttClientPublishResultFactory.Create(pubRecPacket, pubCompPacket); } async Task Receive(CancellationToken cancellationToken) @@ -875,31 +863,29 @@ async Task Request(MqttPacket requestPacket, C packetIdentifier = packetWithIdentifier.PacketIdentifier; } - using (var packetAwaitable = _packetDispatcher.AddAwaitable(packetIdentifier)) + using var packetAwaitable = _packetDispatcher.AddAwaitable(packetIdentifier); + try { - try - { - await Send(requestPacket, cancellationToken).ConfigureAwait(false); - } - catch (Exception exception) - { - _logger.Warning(exception, "Error when sending {0} request packet", requestPacket.GetType().Name); - packetAwaitable.Fail(exception); - } + await Send(requestPacket, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.Warning(exception, "Error when sending {0} request packet", requestPacket.GetType().Name); + packetAwaitable.Fail(exception); + } - try + try + { + return await packetAwaitable.WaitOneAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) + { + if (exception is MqttCommunicationTimedOutException) { - return await packetAwaitable.WaitOneAsync(cancellationToken).ConfigureAwait(false); + _logger.Warning("Timeout while waiting for {0} response packet", typeof(TResponsePacket).Name); } - catch (Exception exception) - { - if (exception is MqttCommunicationTimedOutException) - { - _logger.Warning("Timeout while waiting for {0} response packet", typeof(TResponsePacket).Name); - } - throw; - } + throw; } } @@ -1066,9 +1052,9 @@ async Task TrySendKeepAliveMessages(CancellationToken cancellationToken) switch (exception) { - case OperationCanceledException _: + case OperationCanceledException: return; - case MqttCommunicationException _: + case MqttCommunicationException: _logger.Warning(exception, "Communication error while sending/receiving keep alive packets"); break; default: diff --git a/Source/MQTTnet/MqttClientFactory.cs b/Source/MQTTnet/MqttClientFactory.cs index 8994ee988..746d16ddf 100644 --- a/Source/MQTTnet/MqttClientFactory.cs +++ b/Source/MQTTnet/MqttClientFactory.cs @@ -11,6 +11,8 @@ namespace MQTTnet; +#pragma warning disable CA1822 + public class MqttClientFactory { readonly IMqttNetLogger _logger; @@ -48,6 +50,7 @@ public MqttClientDisconnectOptionsBuilder CreateClientDisconnectOptionsBuilder() } public MqttClientOptionsBuilder CreateClientOptionsBuilder() + { return new MqttClientOptionsBuilder(); } diff --git a/Source/MQTTnet/MqttTopicFilterBuilder.cs b/Source/MQTTnet/MqttTopicFilterBuilder.cs index 2140745a7..a927e5a76 100644 --- a/Source/MQTTnet/MqttTopicFilterBuilder.cs +++ b/Source/MQTTnet/MqttTopicFilterBuilder.cs @@ -6,93 +6,92 @@ using MQTTnet.Protocol; using MQTTnet.Packets; -namespace MQTTnet +namespace MQTTnet; + +public sealed class MqttTopicFilterBuilder { - public sealed class MqttTopicFilterBuilder + /// + /// The quality of service level. + /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message. + /// There are 3 QoS levels in MQTT: + /// - At most once (0): Message gets delivered no time, once or multiple times. + /// - At least once (1): Message gets delivered at least once (one time or more often). + /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). + /// + MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + + /// + /// The MQTT topic. + /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client. + /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator). + /// + string _topic; + bool _noLocal; + bool _retainAsPublished; + MqttRetainHandling _retainHandling = MqttRetainHandling.SendAtSubscribe; + + public MqttTopicFilterBuilder WithTopic(string topic) { - /// - /// The quality of service level. - /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message. - /// There are 3 QoS levels in MQTT: - /// - At most once (0): Message gets delivered no time, once or multiple times. - /// - At least once (1): Message gets delivered at least once (one time or more often). - /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). - /// - MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + _topic = topic; + return this; + } - /// - /// The MQTT topic. - /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client. - /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator). - /// - string _topic; - bool _noLocal; - bool _retainAsPublished; - MqttRetainHandling _retainHandling = MqttRetainHandling.SendAtSubscribe; + public MqttTopicFilterBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel) + { + _qualityOfServiceLevel = qualityOfServiceLevel; + return this; + } - public MqttTopicFilterBuilder WithTopic(string topic) - { - _topic = topic; - return this; - } + public MqttTopicFilterBuilder WithAtLeastOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce; + return this; + } - public MqttTopicFilterBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel) - { - _qualityOfServiceLevel = qualityOfServiceLevel; - return this; - } + public MqttTopicFilterBuilder WithAtMostOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; + return this; + } - public MqttTopicFilterBuilder WithAtLeastOnceQoS() - { - _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce; - return this; - } + public MqttTopicFilterBuilder WithExactlyOnceQoS() + { + _qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce; + return this; + } - public MqttTopicFilterBuilder WithAtMostOnceQoS() - { - _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; - return this; - } + public MqttTopicFilterBuilder WithNoLocal(bool value = true) + { + _noLocal = value; + return this; + } - public MqttTopicFilterBuilder WithExactlyOnceQoS() - { - _qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce; - return this; - } - - public MqttTopicFilterBuilder WithNoLocal(bool value = true) - { - _noLocal = value; - return this; - } - - public MqttTopicFilterBuilder WithRetainAsPublished(bool value = true) - { - _retainAsPublished = value; - return this; - } - - public MqttTopicFilterBuilder WithRetainHandling(MqttRetainHandling value) + public MqttTopicFilterBuilder WithRetainAsPublished(bool value = true) + { + _retainAsPublished = value; + return this; + } + + public MqttTopicFilterBuilder WithRetainHandling(MqttRetainHandling value) + { + _retainHandling = value; + return this; + } + + public MqttTopicFilter Build() + { + if (string.IsNullOrEmpty(_topic)) { - _retainHandling = value; - return this; + throw new MqttProtocolViolationException("Topic is not set."); } - public MqttTopicFilter Build() + return new MqttTopicFilter { - if (string.IsNullOrEmpty(_topic)) - { - throw new MqttProtocolViolationException("Topic is not set."); - } - - return new MqttTopicFilter - { - Topic = _topic, - QualityOfServiceLevel = _qualityOfServiceLevel, - NoLocal = _noLocal, - RetainAsPublished = _retainAsPublished, - RetainHandling = _retainHandling - }; - } + Topic = _topic, + QualityOfServiceLevel = _qualityOfServiceLevel, + NoLocal = _noLocal, + RetainAsPublished = _retainAsPublished, + RetainHandling = _retainHandling + }; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/MqttTopicFilterCompareResult.cs b/Source/MQTTnet/MqttTopicFilterCompareResult.cs index 5ce59a5c8..bc2b7021c 100644 --- a/Source/MQTTnet/MqttTopicFilterCompareResult.cs +++ b/Source/MQTTnet/MqttTopicFilterCompareResult.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet +namespace MQTTnet; + +public enum MqttTopicFilterCompareResult { - public enum MqttTopicFilterCompareResult - { - NoMatch, - - IsMatch, - - FilterInvalid, - - TopicInvalid - } + NoMatch, + + IsMatch, + + FilterInvalid, + + TopicInvalid } \ No newline at end of file diff --git a/Source/MQTTnet/MqttTopicFilterComparer.cs b/Source/MQTTnet/MqttTopicFilterComparer.cs index 72285a82f..4ba748d52 100644 --- a/Source/MQTTnet/MqttTopicFilterComparer.cs +++ b/Source/MQTTnet/MqttTopicFilterComparer.cs @@ -2,177 +2,176 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet +namespace MQTTnet; + +public static class MqttTopicFilterComparer { - public static class MqttTopicFilterComparer + public const char LevelSeparator = '/'; + public const char MultiLevelWildcard = '#'; + public const char SingleLevelWildcard = '+'; + public const char ReservedTopicPrefix = '$'; + + public static unsafe MqttTopicFilterCompareResult Compare(string topic, string filter) { - public const char LevelSeparator = '/'; - public const char MultiLevelWildcard = '#'; - public const char SingleLevelWildcard = '+'; - public const char ReservedTopicPrefix = '$'; + if (string.IsNullOrEmpty(topic)) + { + return MqttTopicFilterCompareResult.TopicInvalid; + } + + if (string.IsNullOrEmpty(filter)) + { + return MqttTopicFilterCompareResult.FilterInvalid; + } + + var filterOffset = 0; + var filterLength = filter.Length; - public static unsafe MqttTopicFilterCompareResult Compare(string topic, string filter) + var topicOffset = 0; + var topicLength = topic.Length; + + fixed (char* topicPointer = topic) + fixed (char* filterPointer = filter) { - if (string.IsNullOrEmpty(topic)) + if (filterLength > topicLength) { - return MqttTopicFilterCompareResult.TopicInvalid; + // It is impossible to create a filter which is longer than the actual topic. + // The only way this can happen is when the last char is a wildcard char. + // sensor/7/temperature >> sensor/7/temperature = Equal + // sensor/+/temperature >> sensor/7/temperature = Equal + // sensor/7/+ >> sensor/7/temperature = Shorter + // sensor/# >> sensor/7/temperature = Shorter + var lastFilterChar = filterPointer[filterLength - 1]; + if (lastFilterChar != MultiLevelWildcard && lastFilterChar != SingleLevelWildcard) + { + return MqttTopicFilterCompareResult.NoMatch; + } } - if (string.IsNullOrEmpty(filter)) + var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard; + var isReservedTopic = topicPointer[0] == ReservedTopicPrefix; + + if (isReservedTopic && filterLength == 1 && isMultiLevelFilter) { - return MqttTopicFilterCompareResult.FilterInvalid; + // It is not allowed to receive i.e. '$foo/bar' with filter '#'. + return MqttTopicFilterCompareResult.NoMatch; } - var filterOffset = 0; - var filterLength = filter.Length; + if (isReservedTopic && filterPointer[0] == SingleLevelWildcard) + { + // It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'. + return MqttTopicFilterCompareResult.NoMatch; + } - var topicOffset = 0; - var topicLength = topic.Length; - - fixed (char* topicPointer = topic) - fixed (char* filterPointer = filter) + if (filterLength == 1 && isMultiLevelFilter) { - if (filterLength > topicLength) - { - // It is impossible to create a filter which is longer than the actual topic. - // The only way this can happen is when the last char is a wildcard char. - // sensor/7/temperature >> sensor/7/temperature = Equal - // sensor/+/temperature >> sensor/7/temperature = Equal - // sensor/7/+ >> sensor/7/temperature = Shorter - // sensor/# >> sensor/7/temperature = Shorter - var lastFilterChar = filterPointer[filterLength - 1]; - if (lastFilterChar != MultiLevelWildcard && lastFilterChar != SingleLevelWildcard) - { - return MqttTopicFilterCompareResult.NoMatch; - } - } - - var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard; - var isReservedTopic = topicPointer[0] == ReservedTopicPrefix; + // Filter '#' matches basically everything. + return MqttTopicFilterCompareResult.IsMatch; + } - if (isReservedTopic && filterLength == 1 && isMultiLevelFilter) + // Go through the filter char by char. + while (filterOffset < filterLength && topicOffset < topicLength) + { + // Check if the current char is a multi level wildcard. The char is only allowed + // at the very las position. + if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1) { - // It is not allowed to receive i.e. '$foo/bar' with filter '#'. - return MqttTopicFilterCompareResult.NoMatch; + return MqttTopicFilterCompareResult.FilterInvalid; } - if (isReservedTopic && filterPointer[0] == SingleLevelWildcard) + if (filterPointer[filterOffset] == topicPointer[topicOffset]) { - // It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'. - return MqttTopicFilterCompareResult.NoMatch; - } + if (topicOffset == topicLength - 1) + { + // Check for e.g. "foo" matching "foo/#" + if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter) + { + return MqttTopicFilterCompareResult.IsMatch; + } - if (filterLength == 1 && isMultiLevelFilter) - { - // Filter '#' matches basically everything. - return MqttTopicFilterCompareResult.IsMatch; - } + // Check for e.g. "foo/" matching "foo/#" + if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter) + { + return MqttTopicFilterCompareResult.IsMatch; + } + } - // Go through the filter char by char. - while (filterOffset < filterLength && topicOffset < topicLength) - { - // Check if the current char is a multi level wildcard. The char is only allowed - // at the very las position. - if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1) + filterOffset++; + topicOffset++; + + // Check if the end was reached and i.e. "foo/bar" matches "foo/bar" + if (filterOffset == filterLength && topicOffset == topicLength) { - return MqttTopicFilterCompareResult.FilterInvalid; + return MqttTopicFilterCompareResult.IsMatch; } - if (filterPointer[filterOffset] == topicPointer[topicOffset]) + var endOfTopic = topicOffset == topicLength; + + if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard) { - if (topicOffset == topicLength - 1) + if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) { - // Check for e.g. "foo" matching "foo/#" - if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter) - { - return MqttTopicFilterCompareResult.IsMatch; - } - - // Check for e.g. "foo/" matching "foo/#" - if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter) - { - return MqttTopicFilterCompareResult.IsMatch; - } + return MqttTopicFilterCompareResult.FilterInvalid; } - filterOffset++; - topicOffset++; - - // Check if the end was reached and i.e. "foo/bar" matches "foo/bar" - if (filterOffset == filterLength && topicOffset == topicLength) + return MqttTopicFilterCompareResult.IsMatch; + } + } + else + { + if (filterPointer[filterOffset] == SingleLevelWildcard) + { + // Check for invalid "+foo" or "a/+foo" subscription + if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) { - return MqttTopicFilterCompareResult.IsMatch; + return MqttTopicFilterCompareResult.FilterInvalid; } - var endOfTopic = topicOffset == topicLength; + // Check for bad "foo+" or "foo+/a" subscription + if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator) + { + return MqttTopicFilterCompareResult.FilterInvalid; + } - if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard) + filterOffset++; + while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator) { - if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } + topicOffset++; + } + if (topicOffset == topicLength && filterOffset == filterLength) + { return MqttTopicFilterCompareResult.IsMatch; } } - else + else if (filterPointer[filterOffset] == MultiLevelWildcard) { - if (filterPointer[filterOffset] == SingleLevelWildcard) + if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) { - // Check for invalid "+foo" or "a/+foo" subscription - if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - // Check for bad "foo+" or "foo+/a" subscription - if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - filterOffset++; - while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator) - { - topicOffset++; - } - - if (topicOffset == topicLength && filterOffset == filterLength) - { - return MqttTopicFilterCompareResult.IsMatch; - } + return MqttTopicFilterCompareResult.FilterInvalid; } - else if (filterPointer[filterOffset] == MultiLevelWildcard) - { - if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - if (filterOffset + 1 != filterLength) - { - return MqttTopicFilterCompareResult.FilterInvalid; - } - - return MqttTopicFilterCompareResult.IsMatch; + if (filterOffset + 1 != filterLength) + { + return MqttTopicFilterCompareResult.FilterInvalid; } - else + + return MqttTopicFilterCompareResult.IsMatch; + } + else + { + // Check for e.g. "foo/bar" matching "foo/+/#". + if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard && + filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter) { - // Check for e.g. "foo/bar" matching "foo/+/#". - if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard && - filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter) - { - return MqttTopicFilterCompareResult.IsMatch; - } - - return MqttTopicFilterCompareResult.NoMatch; + return MqttTopicFilterCompareResult.IsMatch; } + + return MqttTopicFilterCompareResult.NoMatch; } } } - - return MqttTopicFilterCompareResult.NoMatch; } + + return MqttTopicFilterCompareResult.NoMatch; } } \ No newline at end of file diff --git a/Source/MQTTnet/Options/DefaultMqttCertificatesProvider.cs b/Source/MQTTnet/Options/DefaultMqttCertificatesProvider.cs index 3e6084640..5285a2a1b 100644 --- a/Source/MQTTnet/Options/DefaultMqttCertificatesProvider.cs +++ b/Source/MQTTnet/Options/DefaultMqttCertificatesProvider.cs @@ -20,7 +20,7 @@ public DefaultMqttCertificatesProvider(IEnumerable certificates { if (certificates != null) { - _certificates = new X509Certificate2Collection(); + _certificates = []; foreach (var certificate in certificates) { _certificates.Add(certificate); diff --git a/Source/MQTTnet/Options/MqttClientDefaultCertificateValidationHandler.cs b/Source/MQTTnet/Options/MqttClientDefaultCertificateValidationHandler.cs index 608a02162..28ba4eaa4 100644 --- a/Source/MQTTnet/Options/MqttClientDefaultCertificateValidationHandler.cs +++ b/Source/MQTTnet/Options/MqttClientDefaultCertificateValidationHandler.cs @@ -18,7 +18,7 @@ public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs) } if (eventArgs.Chain.ChainStatus.Any( - c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation)) + c => c.Status is X509ChainStatusFlags.RevocationStatusUnknown or X509ChainStatusFlags.Revoked or X509ChainStatusFlags.OfflineRevocation)) { if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateRevocationErrors != true) { diff --git a/Source/MQTTnet/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Options/MqttClientOptionsBuilder.cs index a6e92c60d..3c2f589fa 100644 --- a/Source/MQTTnet/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Options/MqttClientOptionsBuilder.cs @@ -127,7 +127,7 @@ public MqttClientOptionsBuilder WithConnectionUri(Uri uri) ArgumentNullException.ThrowIfNull(uri); var port = uri.IsDefaultPort ? null : (int?)uri.Port; - switch (uri.Scheme.ToLower()) + switch (uri.Scheme.ToLowerInvariant()) { case "tcp": case "mqtt": @@ -307,7 +307,7 @@ public MqttClientOptionsBuilder WithTcpServer(Action optio ArgumentNullException.ThrowIfNull(optionsBuilder); _tcpOptions = new MqttClientTcpOptions(); - optionsBuilder.Invoke(_tcpOptions); + optionsBuilder(_tcpOptions); return this; } @@ -466,11 +466,7 @@ public MqttClientOptionsBuilder WithWillTopic(string willTopic) public MqttClientOptionsBuilder WithWillUserProperty(string name, string value) { - if (_options.WillUserProperties == null) - { - _options.WillUserProperties = new List(); - } - + _options.WillUserProperties ??= []; _options.WillUserProperties.Add(new MqttUserProperty(name, value)); return this; } diff --git a/Source/MQTTnet/Options/MqttClientOptionsValidator.cs b/Source/MQTTnet/Options/MqttClientOptionsValidator.cs index 3da66175f..5b40616c1 100644 --- a/Source/MQTTnet/Options/MqttClientOptionsValidator.cs +++ b/Source/MQTTnet/Options/MqttClientOptionsValidator.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using MQTTnet.Formatter; using MQTTnet.Protocol; @@ -25,12 +24,12 @@ public static void ThrowIfNotSupported(MqttClientOptions options) return; } - if (options.WillContentType?.Any() == true) + if (options.WillContentType?.Length > 0) { Throw(nameof(options.WillContentType)); } - if (options.UserProperties?.Any() == true) + if (options.UserProperties?.Count > 0) { Throw(nameof(options.UserProperties)); } @@ -59,12 +58,12 @@ public static void ThrowIfNotSupported(MqttClientOptions options) // Authentication relevant properties. - if (options.AuthenticationData?.Any() == true) + if (options.AuthenticationData?.Length > 0) { Throw(nameof(options.AuthenticationData)); } - if (options.AuthenticationMethod?.Any() == true) + if (options.AuthenticationMethod?.Length > 0) { Throw(nameof(options.AuthenticationMethod)); } @@ -76,17 +75,17 @@ public static void ThrowIfNotSupported(MqttClientOptions options) Throw(nameof(options.WillPayloadFormatIndicator)); } - if (options.WillContentType?.Any() == true) + if (options.WillContentType?.Length > 0) { Throw(nameof(options.WillContentType)); } - if (options.WillCorrelationData?.Any() == true) + if (options.WillCorrelationData?.Length > 0) { Throw(nameof(options.WillCorrelationData)); } - if (options.WillResponseTopic?.Any() == true) + if (options.WillResponseTopic?.Length > 0) { Throw(nameof(options.WillResponseTopic)); } @@ -101,7 +100,7 @@ public static void ThrowIfNotSupported(MqttClientOptions options) Throw(nameof(options.WillMessageExpiryInterval)); } - if (options.WillUserProperties?.Any() == true) + if (options.WillUserProperties?.Count > 0) { Throw(nameof(options.WillUserProperties)); } diff --git a/Source/MQTTnet/Options/MqttClientWebSocketOptions.cs b/Source/MQTTnet/Options/MqttClientWebSocketOptions.cs index 1e2d9811d..b88e57b00 100644 --- a/Source/MQTTnet/Options/MqttClientWebSocketOptions.cs +++ b/Source/MQTTnet/Options/MqttClientWebSocketOptions.cs @@ -12,6 +12,7 @@ namespace MQTTnet; public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions { public CookieContainer CookieContainer { get; set; } + public WebSocketDeflateOptions DangerousDeflateOptions { get; set; } public ICredentials Credentials { get; set; } @@ -26,7 +27,7 @@ public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions public IDictionary RequestHeaders { get; set; } - public ICollection SubProtocols { get; set; } = new List { "mqtt" }; + public ICollection SubProtocols { get; set; } = ["mqtt"]; public MqttClientTlsOptions TlsOptions { get; set; } = new(); diff --git a/Source/MQTTnet/Options/MqttClientWebSocketOptionsBuilder.cs b/Source/MQTTnet/Options/MqttClientWebSocketOptionsBuilder.cs index fffc77ef9..090c72574 100644 --- a/Source/MQTTnet/Options/MqttClientWebSocketOptionsBuilder.cs +++ b/Source/MQTTnet/Options/MqttClientWebSocketOptionsBuilder.cs @@ -59,7 +59,7 @@ public MqttClientWebSocketOptionsBuilder WithProxyOptions(Action ReceiveAsync(Canc public Task SendAsync(SendMqttEnhancedAuthenticationDataOptions options, CancellationToken cancellationToken = default) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + ArgumentNullException.ThrowIfNull(options); var authPacket = new MqttAuthPacket { diff --git a/Source/MQTTnet/Options/MqttEnhancedAuthenticationExchangeData.cs b/Source/MQTTnet/Options/MqttEnhancedAuthenticationExchangeData.cs index 0e7ceafb8..6fac2d767 100644 --- a/Source/MQTTnet/Options/MqttEnhancedAuthenticationExchangeData.cs +++ b/Source/MQTTnet/Options/MqttEnhancedAuthenticationExchangeData.cs @@ -41,5 +41,5 @@ public class MqttEnhancedAuthenticationExchangeData /// The feature is very similar to the HTTP header concept. /// Hint: MQTT 5 feature only. /// - public List UserProperties { get; } + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs b/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs index a467edb8b..1a63eeb74 100644 --- a/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs +++ b/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs @@ -5,16 +5,15 @@ using MQTTnet.Packets; using System; -namespace MQTTnet.PacketDispatcher +namespace MQTTnet.PacketDispatcher; + +public interface IMqttPacketAwaitable : IDisposable { - public interface IMqttPacketAwaitable : IDisposable - { - MqttPacketAwaitableFilter Filter { get; } - - void Complete(MqttPacket packet); + MqttPacketAwaitableFilter Filter { get; } + + void Complete(MqttPacket packet); - void Fail(Exception exception); + void Fail(Exception exception); - void Cancel(); - } + void Cancel(); } \ No newline at end of file diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs index 895e2b530..f751a9071 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs @@ -9,57 +9,56 @@ using System.Threading.Tasks; using MQTTnet.Internal; -namespace MQTTnet.PacketDispatcher +namespace MQTTnet.PacketDispatcher; + +public sealed class MqttPacketAwaitable : IMqttPacketAwaitable where TPacket : MqttPacket { - public sealed class MqttPacketAwaitable : IMqttPacketAwaitable where TPacket : MqttPacket - { - readonly AsyncTaskCompletionSource _promise = new AsyncTaskCompletionSource(); - readonly MqttPacketDispatcher _owningPacketDispatcher; + readonly AsyncTaskCompletionSource _promise = new(); + readonly MqttPacketDispatcher _owningPacketDispatcher; - public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher) - { - Filter = new MqttPacketAwaitableFilter - { - Type = typeof(TPacket), - Identifier = packetIdentifier - }; - - _owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher)); - } - - public MqttPacketAwaitableFilter Filter { get; } - - public async Task WaitOneAsync(CancellationToken cancellationToken) + public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher) + { + Filter = new MqttPacketAwaitableFilter { - using (cancellationToken.Register(() => Fail(new MqttCommunicationTimedOutException()))) - { - var packet = await _promise.Task.ConfigureAwait(false); - return (TPacket)packet; - } - } + Type = typeof(TPacket), + Identifier = packetIdentifier + }; - public void Complete(MqttPacket packet) - { - if (packet == null) throw new ArgumentNullException(nameof(packet)); + _owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher)); + } - _promise.TrySetResult(packet); - } + public MqttPacketAwaitableFilter Filter { get; } - public void Fail(Exception exception) + public async Task WaitOneAsync(CancellationToken cancellationToken) + { + await using (cancellationToken.Register(() => Fail(new MqttCommunicationTimedOutException()))) { - if (exception == null) throw new ArgumentNullException(nameof(exception)); - - _promise.TrySetException(exception); + var packet = await _promise.Task.ConfigureAwait(false); + return (TPacket)packet; } + } - public void Cancel() - { - _promise.TrySetCanceled(); - } + public void Complete(MqttPacket packet) + { + ArgumentNullException.ThrowIfNull(packet); - public void Dispose() - { - _owningPacketDispatcher.RemoveAwaitable(this); - } + _promise.TrySetResult(packet); + } + + public void Fail(Exception exception) + { + ArgumentNullException.ThrowIfNull(exception); + + _promise.TrySetException(exception); + } + + public void Cancel() + { + _promise.TrySetCanceled(); + } + + public void Dispose() + { + _owningPacketDispatcher.RemoveAwaitable(this); } } \ No newline at end of file diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs index ab94969fa..6c551589d 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs @@ -4,12 +4,11 @@ using System; -namespace MQTTnet.PacketDispatcher +namespace MQTTnet.PacketDispatcher; + +public sealed class MqttPacketAwaitableFilter { - public sealed class MqttPacketAwaitableFilter - { - public Type Type { get; set; } - - public ushort Identifier { get; set; } - } + public Type Type { get; set; } + + public ushort Identifier { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs index fd71406de..ae4df439b 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs @@ -6,131 +6,127 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.PacketDispatcher -{ - public sealed class MqttPacketDispatcher : IDisposable - { - readonly List _waiters = new List(); +namespace MQTTnet.PacketDispatcher; - bool _isDisposed; +public sealed class MqttPacketDispatcher : IDisposable +{ + readonly List _waiters = []; - public MqttPacketAwaitable AddAwaitable(ushort packetIdentifier) where TResponsePacket : MqttPacket - { - var awaitable = new MqttPacketAwaitable(packetIdentifier, this); + bool _isDisposed; - lock (_waiters) - { - _waiters.Add(awaitable); - } + public MqttPacketAwaitable AddAwaitable(ushort packetIdentifier) where TResponsePacket : MqttPacket + { + var awaitable = new MqttPacketAwaitable(packetIdentifier, this); - return awaitable; + lock (_waiters) + { + _waiters.Add(awaitable); } - public void CancelAll() + return awaitable; + } + + public void CancelAll() + { + lock (_waiters) { - lock (_waiters) + foreach (var awaitable in _waiters) { - foreach (var awaitable in _waiters) - { - awaitable.Cancel(); - } - - _waiters.Clear(); + awaitable.Cancel(); } - } - public void Dispose() - { - Dispose(new ObjectDisposedException(nameof(MqttPacketDispatcher))); + _waiters.Clear(); } + } - public void Dispose(Exception exception) - { - ArgumentNullException.ThrowIfNull(exception); + public void Dispose() + { + Dispose(new ObjectDisposedException(nameof(MqttPacketDispatcher))); + } - lock (_waiters) - { - FailAll(exception); + public void Dispose(Exception exception) + { + ArgumentNullException.ThrowIfNull(exception); - // Make sure that no task can start waiting after this instance is already disposed. - // This will prevent unexpected freezes. - _isDisposed = true; - } + lock (_waiters) + { + FailAll(exception); + + // Make sure that no task can start waiting after this instance is already disposed. + // This will prevent unexpected freezes. + _isDisposed = true; } + } - public void FailAll(Exception exception) - { - ArgumentNullException.ThrowIfNull(exception); + public void FailAll(Exception exception) + { + ArgumentNullException.ThrowIfNull(exception); - lock (_waiters) + lock (_waiters) + { + foreach (var awaitable in _waiters) { - foreach (var awaitable in _waiters) - { - awaitable.Fail(exception); - } - - _waiters.Clear(); + awaitable.Fail(exception); } + + _waiters.Clear(); } + } - public void RemoveAwaitable(IMqttPacketAwaitable awaitable) - { - ArgumentNullException.ThrowIfNull(awaitable); + public void RemoveAwaitable(IMqttPacketAwaitable awaitable) + { + ArgumentNullException.ThrowIfNull(awaitable); - lock (_waiters) - { - _waiters.Remove(awaitable); - } + lock (_waiters) + { + _waiters.Remove(awaitable); } + } + + public bool TryDispatch(MqttPacket packet) + { + ArgumentNullException.ThrowIfNull(packet); - public bool TryDispatch(MqttPacket packet) + ushort identifier = 0; + if (packet is MqttPacketWithIdentifier packetWithIdentifier) { - ArgumentNullException.ThrowIfNull(packet); + identifier = packetWithIdentifier.PacketIdentifier; + } - ushort identifier = 0; - if (packet is MqttPacketWithIdentifier packetWithIdentifier) - { - identifier = packetWithIdentifier.PacketIdentifier; - } + var packetType = packet.GetType(); + var waiters = new List(); - var packetType = packet.GetType(); - var waiters = new List(); + lock (_waiters) + { + ThrowIfDisposed(); - lock (_waiters) + for (var i = _waiters.Count - 1; i >= 0; i--) { - ThrowIfDisposed(); + var entry = _waiters[i]; - for (var i = _waiters.Count - 1; i >= 0; i--) + // Note: The PingRespPacket will also arrive here and has NO identifier but there + // is code which waits for it. So the code must be able to deal with filters which + // are referring to the type only (identifier is 0)! + if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier) { - var entry = _waiters[i]; - - // Note: The PingRespPacket will also arrive here and has NO identifier but there - // is code which waits for it. So the code must be able to deal with filters which - // are referring to the type only (identifier is 0)! - if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier) - { - continue; - } - - waiters.Add(entry); - _waiters.RemoveAt(i); + continue; } - } - foreach (var matchingEntry in waiters) - { - matchingEntry.Complete(packet); + waiters.Add(entry); + _waiters.RemoveAt(i); } - - return waiters.Count > 0; } - void ThrowIfDisposed() + foreach (var matchingEntry in waiters) { - if (_isDisposed) - { - throw new ObjectDisposedException(nameof(MqttPacketDispatcher)); - } + matchingEntry.Complete(packet); } + + return waiters.Count > 0; + } + + void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_isDisposed, this); } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index f9e83f35d..b91ee9f39 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -5,77 +5,76 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttConnectPacket : MqttPacket { - public sealed class MqttConnectPacket : MqttPacket - { - public byte[] AuthenticationData { get; set; } + public byte[] AuthenticationData { get; set; } - public string AuthenticationMethod { get; set; } + public string AuthenticationMethod { get; set; } - /// - /// Also called "Clean Start" in MQTTv5. - /// - public bool CleanSession { get; set; } + /// + /// Also called "Clean Start" in MQTTv5. + /// + public bool CleanSession { get; set; } - public string ClientId { get; set; } + public string ClientId { get; set; } - public byte[] WillCorrelationData { get; set; } + public byte[] WillCorrelationData { get; set; } - public ushort KeepAlivePeriod { get; set; } - - public uint MaximumPacketSize { get; set; } + public ushort KeepAlivePeriod { get; set; } - public byte[] Password { get; set; } + public uint MaximumPacketSize { get; set; } - public ushort ReceiveMaximum { get; set; } + public byte[] Password { get; set; } - public bool RequestProblemInformation { get; set; } + public ushort ReceiveMaximum { get; set; } - public bool RequestResponseInformation { get; set; } + public bool RequestProblemInformation { get; set; } - public string WillResponseTopic { get; set; } + public bool RequestResponseInformation { get; set; } - public uint SessionExpiryInterval { get; set; } + public string WillResponseTopic { get; set; } - public ushort TopicAliasMaximum { get; set; } + public uint SessionExpiryInterval { get; set; } - public string Username { get; set; } + public ushort TopicAliasMaximum { get; set; } - public List UserProperties { get; set; } + public string Username { get; set; } - public string WillContentType { get; set; } + public List UserProperties { get; set; } - public uint WillDelayInterval { get; set; } + public string WillContentType { get; set; } - public bool WillFlag { get; set; } + public uint WillDelayInterval { get; set; } - public byte[] WillMessage { get; set; } + public bool WillFlag { get; set; } - public uint WillMessageExpiryInterval { get; set; } + public byte[] WillMessage { get; set; } - public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; + public uint WillMessageExpiryInterval { get; set; } - public MqttQualityOfServiceLevel WillQoS { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; + public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - public bool WillRetain { get; set; } + public MqttQualityOfServiceLevel WillQoS { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; - public string WillTopic { get; set; } + public bool WillRetain { get; set; } - public List WillUserProperties { get; set; } + public string WillTopic { get; set; } - public bool TryPrivate { get; set; } - - public override string ToString() - { - var passwordText = string.Empty; + public List WillUserProperties { get; set; } - if (Password != null) - { - passwordText = "****"; - } + public bool TryPrivate { get; set; } - return $"Connect: [ClientId={ClientId}] [Username={Username}] [Password={passwordText}] [KeepAlivePeriod={KeepAlivePeriod}] [CleanSession={CleanSession}]"; + public override string ToString() + { + var passwordText = string.Empty; + + if (Password != null) + { + passwordText = "****"; } + + return $"Connect: [ClientId={ClientId}] [Username={Username}] [Password={passwordText}] [KeepAlivePeriod={KeepAlivePeriod}] [CleanSession={CleanSession}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs index 8fe505a3c..546981706 100644 --- a/Source/MQTTnet/Packets/MqttDisconnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttDisconnectPacket.cs @@ -5,38 +5,37 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttDisconnectPacket : MqttPacket { - public sealed class MqttDisconnectPacket : MqttPacket + /// + /// Added in MQTTv5. + /// + public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection; + + /// + /// Added in MQTTv5. + /// + public string ReasonString { get; set; } + + /// + /// Added in MQTTv5. + /// + public string ServerReference { get; set; } + + /// + /// Added in MQTTv5. + /// + public uint SessionExpiryInterval { get; set; } + + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } + + public override string ToString() { - /// - /// Added in MQTTv5. - /// - public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection; - - /// - /// Added in MQTTv5. - /// - public string ReasonString { get; set; } - - /// - /// Added in MQTTv5. - /// - public string ServerReference { get; set; } - - /// - /// Added in MQTTv5. - /// - public uint SessionExpiryInterval { get; set; } - - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } - - public override string ToString() - { - return $"Disconnect: [ReasonCode={ReasonCode}] [ReasonString={ReasonString}] [ServerReference={ServerReference}] [SessionExpiryInterval={SessionExpiryInterval}]"; - } + return $"Disconnect: [ReasonCode={ReasonCode}] [ReasonString={ReasonString}] [ServerReference={ServerReference}] [SessionExpiryInterval={SessionExpiryInterval}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPacket.cs b/Source/MQTTnet/Packets/MqttPacket.cs index 1e07e42fe..cfcaa0cc0 100644 --- a/Source/MQTTnet/Packets/MqttPacket.cs +++ b/Source/MQTTnet/Packets/MqttPacket.cs @@ -4,6 +4,4 @@ namespace MQTTnet.Packets; -public abstract class MqttPacket -{ -} \ No newline at end of file +public abstract class MqttPacket; \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPacketExtensions.cs b/Source/MQTTnet/Packets/MqttPacketExtensions.cs index 90be0ef7d..d2c55aeaa 100644 --- a/Source/MQTTnet/Packets/MqttPacketExtensions.cs +++ b/Source/MQTTnet/Packets/MqttPacketExtensions.cs @@ -12,87 +12,24 @@ public static string GetRfcName(this MqttPacket packet) { ArgumentNullException.ThrowIfNull(packet); - switch (packet) + return packet switch { - case MqttConnectPacket _: - { - return "CONNECT"; - } - - case MqttConnAckPacket _: - { - return "CONNACK"; - } - - case MqttAuthPacket _: - { - return "AUTH"; - } - - case MqttDisconnectPacket _: - { - return "DISCONNECT"; - } - - case MqttPingReqPacket _: - { - return "PINGREQ"; - } - - case MqttPingRespPacket _: - { - return "PINGRESP"; - } - - case MqttSubscribePacket _: - { - return "SUBSCRIBE"; - } - - case MqttSubAckPacket _: - { - return "SUBACK"; - } - - case MqttUnsubscribePacket _: - { - return "UNSUBSCRIBE"; - } - - case MqttUnsubAckPacket _: - { - return "UNSUBACK"; - } - - case MqttPublishPacket _: - { - return "PUBLISH"; - } - - case MqttPubAckPacket _: - { - return "PUBACK"; - } - - case MqttPubRelPacket _: - { - return "PUBREL"; - } - - case MqttPubRecPacket _: - { - return "PUBREC"; - } - - case MqttPubCompPacket _: - { - return "PUBCOMP"; - } - - default: - { - return packet.GetType().Name; - } - } + MqttConnectPacket => "CONNECT", + MqttConnAckPacket => "CONNACK", + MqttAuthPacket => "AUTH", + MqttDisconnectPacket => "DISCONNECT", + MqttPingReqPacket => "PINGREQ", + MqttPingRespPacket => "PINGRESP", + MqttSubscribePacket => "SUBSCRIBE", + MqttSubAckPacket => "SUBACK", + MqttUnsubscribePacket => "UNSUBSCRIBE", + MqttUnsubAckPacket => "UNSUBACK", + MqttPublishPacket => "PUBLISH", + MqttPubAckPacket => "PUBACK", + MqttPubRelPacket => "PUBREL", + MqttPubRecPacket => "PUBREC", + MqttPubCompPacket => "PUBCOMP", + _ => packet.GetType().Name + }; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPacketWithIdentifier.cs b/Source/MQTTnet/Packets/MqttPacketWithIdentifier.cs index 99caa7b71..4589e2c8f 100644 --- a/Source/MQTTnet/Packets/MqttPacketWithIdentifier.cs +++ b/Source/MQTTnet/Packets/MqttPacketWithIdentifier.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public abstract class MqttPacketWithIdentifier : MqttPacket { - public abstract class MqttPacketWithIdentifier : MqttPacket - { - public ushort PacketIdentifier { get; set; } - } -} + public ushort PacketIdentifier { get; set; } +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPingReqPacket.cs b/Source/MQTTnet/Packets/MqttPingReqPacket.cs index 9ca47649c..de0022897 100644 --- a/Source/MQTTnet/Packets/MqttPingReqPacket.cs +++ b/Source/MQTTnet/Packets/MqttPingReqPacket.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttPingReqPacket : MqttPacket { - public sealed class MqttPingReqPacket : MqttPacket - { - // This is a minor performance improvement. - public static readonly MqttPingReqPacket Instance = new MqttPingReqPacket(); + // This is a minor performance improvement. + public static readonly MqttPingReqPacket Instance = new(); - public override string ToString() - { - return "PingReq"; - } + public override string ToString() + { + return "PingReq"; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPubRecPacket.cs b/Source/MQTTnet/Packets/MqttPubRecPacket.cs index 3e080e7cb..624c33158 100644 --- a/Source/MQTTnet/Packets/MqttPubRecPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubRecPacket.cs @@ -5,28 +5,27 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttPubRecPacket : MqttPacketWithIdentifier { - public sealed class MqttPubRecPacket : MqttPacketWithIdentifier - { - /// - /// Added in MQTTv5. - /// - public MqttPubRecReasonCode ReasonCode { get; set; } = MqttPubRecReasonCode.Success; + /// + /// Added in MQTTv5. + /// + public MqttPubRecReasonCode ReasonCode { get; set; } = MqttPubRecReasonCode.Success; - /// - /// Added in MQTTv5. - /// - public string ReasonString { get; set; } + /// + /// Added in MQTTv5. + /// + public string ReasonString { get; set; } - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } - public override string ToString() - { - return $"PubRec: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; - } + public override string ToString() + { + return $"PubRec: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPubRelPacket.cs b/Source/MQTTnet/Packets/MqttPubRelPacket.cs index 9631da37d..190fde9cc 100644 --- a/Source/MQTTnet/Packets/MqttPubRelPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubRelPacket.cs @@ -5,28 +5,27 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttPubRelPacket : MqttPacketWithIdentifier { - public sealed class MqttPubRelPacket : MqttPacketWithIdentifier - { - /// - /// Added in MQTTv5. - /// - public MqttPubRelReasonCode ReasonCode { get; set; } = MqttPubRelReasonCode.Success; + /// + /// Added in MQTTv5. + /// + public MqttPubRelReasonCode ReasonCode { get; set; } = MqttPubRelReasonCode.Success; - /// - /// Added in MQTTv5. - /// - public string ReasonString { get; set; } + /// + /// Added in MQTTv5. + /// + public string ReasonString { get; set; } - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } - public override string ToString() - { - return $"PubRel: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; - } + public override string ToString() + { + return $"PubRel: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 9edc789e2..11e709695 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -21,7 +21,7 @@ public sealed class MqttPublishPacket : MqttPacketWithIdentifier public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - public ArraySegment PayloadSegment { set { Payload = new ReadOnlySequence(value); } } + public ArraySegment PayloadSegment { set => Payload = new ReadOnlySequence(value); } public ReadOnlySequence Payload { get; set; } diff --git a/Source/MQTTnet/Packets/MqttSubAckPacket.cs b/Source/MQTTnet/Packets/MqttSubAckPacket.cs index e9887c813..013d7c252 100644 --- a/Source/MQTTnet/Packets/MqttSubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttSubAckPacket.cs @@ -6,30 +6,29 @@ using System.Linq; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttSubAckPacket : MqttPacketWithIdentifier { - public sealed class MqttSubAckPacket : MqttPacketWithIdentifier - { - /// - /// Reason Code is used in MQTTv5.0.0 and backward compatible to v.3.1.1. Return Code is used in MQTTv3.1.1 - /// - public List ReasonCodes { get; set; } + /// + /// Reason Code is used in MQTTv5.0.0 and backward compatible to v.3.1.1. Return Code is used in MQTTv3.1.1 + /// + public List ReasonCodes { get; set; } - /// - /// Added in MQTTv5. - /// - public string ReasonString { get; set; } + /// + /// Added in MQTTv5. + /// + public string ReasonString { get; set; } - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } - public override string ToString() - { - var reasonCodesText = string.Join(",", ReasonCodes.Select(f => f.ToString())); + public override string ToString() + { + var reasonCodesText = string.Join(",", ReasonCodes.Select(f => f.ToString())); - return $"SubAck: [PacketIdentifier={PacketIdentifier}] [ReasonCode={reasonCodesText}]"; - } + return $"SubAck: [PacketIdentifier={PacketIdentifier}] [ReasonCode={reasonCodesText}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index d1024f4b6..853037edb 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -14,7 +14,7 @@ public sealed class MqttSubscribePacket : MqttPacketWithIdentifier /// public uint SubscriptionIdentifier { get; set; } - public List TopicFilters { get; set; } = new(); + public List TopicFilters { get; set; } = []; /// /// Added in MQTTv5. diff --git a/Source/MQTTnet/Packets/MqttTopicFilter.cs b/Source/MQTTnet/Packets/MqttTopicFilter.cs index 41876e71d..196be2aaf 100644 --- a/Source/MQTTnet/Packets/MqttTopicFilter.cs +++ b/Source/MQTTnet/Packets/MqttTopicFilter.cs @@ -4,52 +4,51 @@ using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttTopicFilter { - public sealed class MqttTopicFilter + /// + /// Gets or sets a value indicating whether the sender will not receive its own published application messages. + /// MQTT 5.0.0+ feature. + /// + public bool NoLocal { get; set; } + + /// + /// Gets or sets the quality of service level. + /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message + /// that defines the guarantee of delivery for a specific message. + /// There are 3 QoS levels in MQTT: + /// - At most once (0): Message gets delivered no time, once or multiple times. + /// - At least once (1): Message gets delivered at least once (one time or more often). + /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). + /// + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + + /// + /// Gets or sets a value indicating whether messages are retained as published or not. + /// MQTT 5.0.0+ feature. + /// + public bool RetainAsPublished { get; set; } + + /// + /// Gets or sets the retain handling. + /// MQTT 5.0.0+ feature. + /// + public MqttRetainHandling RetainHandling { get; set; } = MqttRetainHandling.SendAtSubscribe; + + /// + /// Gets or sets the MQTT topic. + /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected + /// client. + /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level + /// separator). + /// + public string Topic { get; set; } + + public override string ToString() { - /// - /// Gets or sets a value indicating whether the sender will not receive its own published application messages. - /// MQTT 5.0.0+ feature. - /// - public bool NoLocal { get; set; } - - /// - /// Gets or sets the quality of service level. - /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message - /// that defines the guarantee of delivery for a specific message. - /// There are 3 QoS levels in MQTT: - /// - At most once (0): Message gets delivered no time, once or multiple times. - /// - At least once (1): Message gets delivered at least once (one time or more often). - /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). - /// - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } - - /// - /// Gets or sets a value indicating whether messages are retained as published or not. - /// MQTT 5.0.0+ feature. - /// - public bool RetainAsPublished { get; set; } - - /// - /// Gets or sets the retain handling. - /// MQTT 5.0.0+ feature. - /// - public MqttRetainHandling RetainHandling { get; set; } = MqttRetainHandling.SendAtSubscribe; - - /// - /// Gets or sets the MQTT topic. - /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected - /// client. - /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level - /// separator). - /// - public string Topic { get; set; } - - public override string ToString() - { - return - $"TopicFilter: [Topic={Topic}] [QualityOfServiceLevel={QualityOfServiceLevel}] [NoLocal={NoLocal}] [RetainAsPublished={RetainAsPublished}] [RetainHandling={RetainHandling}]"; - } + return + $"TopicFilter: [Topic={Topic}] [QualityOfServiceLevel={QualityOfServiceLevel}] [NoLocal={NoLocal}] [RetainAsPublished={RetainAsPublished}] [RetainHandling={RetainHandling}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs index 1d2d32f5e..1c79ebfbe 100644 --- a/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttUnsubAckPacket.cs @@ -6,34 +6,33 @@ using System.Linq; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttUnsubAckPacket : MqttPacketWithIdentifier { - public sealed class MqttUnsubAckPacket : MqttPacketWithIdentifier - { - /// - /// Added in MQTTv5. - /// - public List ReasonCodes { get; set; } + /// + /// Added in MQTTv5. + /// + public List ReasonCodes { get; set; } - /// - /// Added in MQTTv5. - /// - public string ReasonString { get; set; } + /// + /// Added in MQTTv5. + /// + public string ReasonString { get; set; } - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } - public override string ToString() + public override string ToString() + { + var reasonCodesText = string.Empty; + if (ReasonCodes != null) { - var reasonCodesText = string.Empty; - if (ReasonCodes != null) - { - reasonCodesText = string.Join(",", ReasonCodes?.Select(f => f.ToString())); - } - - return $"UnsubAck: [PacketIdentifier={PacketIdentifier}] [ReasonCodes={reasonCodesText}] [ReasonString={ReasonString}]"; + reasonCodesText = string.Join(",", ReasonCodes?.Select(f => f.ToString())); } + + return $"UnsubAck: [PacketIdentifier={PacketIdentifier}] [ReasonCodes={reasonCodesText}] [ReasonString={ReasonString}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs b/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs index 698bb3301..4053c1fe8 100644 --- a/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Packets; public sealed class MqttUnsubscribePacket : MqttPacketWithIdentifier { - public List TopicFilters { get; set; } = new(); + public List TopicFilters { get; set; } = []; /// /// Added in MQTTv5. diff --git a/Source/MQTTnet/Packets/MqttUserProperty.cs b/Source/MQTTnet/Packets/MqttUserProperty.cs index 1486dd865..43cbad35f 100644 --- a/Source/MQTTnet/Packets/MqttUserProperty.cs +++ b/Source/MQTTnet/Packets/MqttUserProperty.cs @@ -18,9 +18,9 @@ public MqttUserProperty(string name, string value) public string Value { get; } - public override bool Equals(object other) + public override bool Equals(object obj) { - return Equals(other as MqttUserProperty); + return Equals(obj as MqttUserProperty); } public bool Equals(MqttUserProperty other) diff --git a/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs b/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs index 1f59e1dd9..8834608d2 100644 --- a/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttAuthenticateReasonCode.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttAuthenticateReasonCode { - public enum MqttAuthenticateReasonCode - { - Success = 0, - ContinueAuthentication = 24, - ReAuthenticate = 25 - } -} + Success = 0, + ContinueAuthentication = 24, + ReAuthenticate = 25 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs b/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs index 74977df9f..929d3df75 100644 --- a/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttConnectReasonCode.cs @@ -2,31 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttConnectReasonCode { - public enum MqttConnectReasonCode - { - Success = 0, - UnspecifiedError = 128, - MalformedPacket = 129, - ProtocolError = 130, - ImplementationSpecificError = 131, - UnsupportedProtocolVersion = 132, - ClientIdentifierNotValid = 133, - BadUserNameOrPassword = 134, - NotAuthorized = 135, - ServerUnavailable = 136, - ServerBusy = 137, - Banned = 138, - BadAuthenticationMethod = 140, - TopicNameInvalid = 144, - PacketTooLarge = 149, - QuotaExceeded = 151, - PayloadFormatInvalid = 153, - RetainNotSupported = 154, - QoSNotSupported = 155, - UseAnotherServer = 156, - ServerMoved = 157, - ConnectionRateExceeded = 159 - } -} + Success = 0, + UnspecifiedError = 128, + MalformedPacket = 129, + ProtocolError = 130, + ImplementationSpecificError = 131, + UnsupportedProtocolVersion = 132, + ClientIdentifierNotValid = 133, + BadUserNameOrPassword = 134, + NotAuthorized = 135, + ServerUnavailable = 136, + ServerBusy = 137, + Banned = 138, + BadAuthenticationMethod = 140, + TopicNameInvalid = 144, + PacketTooLarge = 149, + QuotaExceeded = 151, + PayloadFormatInvalid = 153, + RetainNotSupported = 154, + QoSNotSupported = 155, + UseAnotherServer = 156, + ServerMoved = 157, + ConnectionRateExceeded = 159 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttConnectReturnCode.cs b/Source/MQTTnet/Protocol/MqttConnectReturnCode.cs index 0687fe70f..5b5c7fa46 100644 --- a/Source/MQTTnet/Protocol/MqttConnectReturnCode.cs +++ b/Source/MQTTnet/Protocol/MqttConnectReturnCode.cs @@ -2,15 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttConnectReturnCode { - public enum MqttConnectReturnCode - { - ConnectionAccepted = 0x00, - ConnectionRefusedUnacceptableProtocolVersion = 0x01, - ConnectionRefusedIdentifierRejected = 0x02, - ConnectionRefusedServerUnavailable = 0x03, - ConnectionRefusedBadUsernameOrPassword = 0x04, - ConnectionRefusedNotAuthorized = 0x05 - } -} + ConnectionAccepted = 0x00, + ConnectionRefusedUnacceptableProtocolVersion = 0x01, + ConnectionRefusedIdentifierRejected = 0x02, + ConnectionRefusedServerUnavailable = 0x03, + ConnectionRefusedBadUsernameOrPassword = 0x04, + ConnectionRefusedNotAuthorized = 0x05 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttControlPacketType.cs b/Source/MQTTnet/Protocol/MqttControlPacketType.cs index 37a3fc5dc..d2d312e24 100644 --- a/Source/MQTTnet/Protocol/MqttControlPacketType.cs +++ b/Source/MQTTnet/Protocol/MqttControlPacketType.cs @@ -2,24 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttControlPacketType { - public enum MqttControlPacketType - { - Connect = 1, - ConnAck = 2, - Publish = 3, - PubAck = 4, - PubRec = 5, - PubRel = 6, - PubComp = 7, - Subscribe = 8, - SubAck = 9, - Unsubscribe = 10, - UnsubAck = 11, - PingReq = 12, - PingResp = 13, - Disconnect = 14, - Auth = 15 - } -} + Connect = 1, + ConnAck = 2, + Publish = 3, + PubAck = 4, + PubRec = 5, + PubRel = 6, + PubComp = 7, + Subscribe = 8, + SubAck = 9, + Unsubscribe = 10, + UnsubAck = 11, + PingReq = 12, + PingResp = 13, + Disconnect = 14, + Auth = 15 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs b/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs index b3307f7a9..4e9849df8 100644 --- a/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttDisconnectReasonCode.cs @@ -2,38 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttDisconnectReasonCode { - public enum MqttDisconnectReasonCode - { - NormalDisconnection = 0, - DisconnectWithWillMessage = 4, - UnspecifiedError = 128, - MalformedPacket = 129, - ProtocolError = 130, - ImplementationSpecificError = 131, - NotAuthorized = 135, - ServerBusy = 137, - ServerShuttingDown = 139, - KeepAliveTimeout = 141, - SessionTakenOver = 142, - TopicFilterInvalid = 143, - TopicNameInvalid = 144, - ReceiveMaximumExceeded = 147, - TopicAliasInvalid = 148, - PacketTooLarge = 149, - MessageRateTooHigh = 150, - QuotaExceeded = 151, - AdministrativeAction = 152, - PayloadFormatInvalid = 153, - RetainNotSupported = 154, - QoSNotSupported = 155, - UseAnotherServer = 156, - ServerMoved = 157, - SharedSubscriptionsNotSupported = 158, - ConnectionRateExceeded = 159, - MaximumConnectTime = 160, - SubscriptionIdentifiersNotSupported = 161, - WildcardSubscriptionsNotSupported = 162 - } -} + NormalDisconnection = 0, + DisconnectWithWillMessage = 4, + UnspecifiedError = 128, + MalformedPacket = 129, + ProtocolError = 130, + ImplementationSpecificError = 131, + NotAuthorized = 135, + ServerBusy = 137, + ServerShuttingDown = 139, + KeepAliveTimeout = 141, + SessionTakenOver = 142, + TopicFilterInvalid = 143, + TopicNameInvalid = 144, + ReceiveMaximumExceeded = 147, + TopicAliasInvalid = 148, + PacketTooLarge = 149, + MessageRateTooHigh = 150, + QuotaExceeded = 151, + AdministrativeAction = 152, + PayloadFormatInvalid = 153, + RetainNotSupported = 154, + QoSNotSupported = 155, + UseAnotherServer = 156, + ServerMoved = 157, + SharedSubscriptionsNotSupported = 158, + ConnectionRateExceeded = 159, + MaximumConnectTime = 160, + SubscriptionIdentifiersNotSupported = 161, + WildcardSubscriptionsNotSupported = 162 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs b/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs index a4446c4b3..f43a22e99 100644 --- a/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs +++ b/Source/MQTTnet/Protocol/MqttPayloadFormatIndicator.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttPayloadFormatIndicator { - public enum MqttPayloadFormatIndicator - { - Unspecified = 0, - CharacterData = 1 - } -} + Unspecified = 0, + CharacterData = 1 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPorts.cs b/Source/MQTTnet/Protocol/MqttPorts.cs index b2ea8f04d..74dc27867 100644 --- a/Source/MQTTnet/Protocol/MqttPorts.cs +++ b/Source/MQTTnet/Protocol/MqttPorts.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public static class MqttPorts { - public static class MqttPorts - { - public const int Default = 1883; + public const int Default = 1883; - public const int Secure = 8883; - } + public const int Secure = 8883; } \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPropertyId.cs b/Source/MQTTnet/Protocol/MqttPropertyId.cs index bce6c2301..4113dc4f3 100644 --- a/Source/MQTTnet/Protocol/MqttPropertyId.cs +++ b/Source/MQTTnet/Protocol/MqttPropertyId.cs @@ -2,38 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttPropertyId { - public enum MqttPropertyId - { - None = 0, - - PayloadFormatIndicator = 1, - MessageExpiryInterval = 2, - ContentType = 3, - ResponseTopic = 8, - CorrelationData = 9, - SubscriptionIdentifier = 11, - SessionExpiryInterval = 17, - AssignedClientIdentifier = 18, - ServerKeepAlive = 19, - AuthenticationMethod = 21, - AuthenticationData = 22, - RequestProblemInformation = 23, - WillDelayInterval = 24, - RequestResponseInformation = 25, - ResponseInformation = 26, - ServerReference = 28, - ReasonString = 31, - ReceiveMaximum = 33, - TopicAliasMaximum = 34, - TopicAlias = 35, - MaximumQoS = 36, - RetainAvailable = 37, - UserProperty = 38, - MaximumPacketSize = 39, - WildcardSubscriptionAvailable = 40, - SubscriptionIdentifiersAvailable = 41, - SharedSubscriptionAvailable = 42 - } -} + None = 0, + + PayloadFormatIndicator = 1, + MessageExpiryInterval = 2, + ContentType = 3, + ResponseTopic = 8, + CorrelationData = 9, + SubscriptionIdentifier = 11, + SessionExpiryInterval = 17, + AssignedClientIdentifier = 18, + ServerKeepAlive = 19, + AuthenticationMethod = 21, + AuthenticationData = 22, + RequestProblemInformation = 23, + WillDelayInterval = 24, + RequestResponseInformation = 25, + ResponseInformation = 26, + ServerReference = 28, + ReasonString = 31, + ReceiveMaximum = 33, + TopicAliasMaximum = 34, + TopicAlias = 35, + MaximumQoS = 36, + RetainAvailable = 37, + UserProperty = 38, + MaximumPacketSize = 39, + WildcardSubscriptionAvailable = 40, + SubscriptionIdentifiersAvailable = 41, + SharedSubscriptionAvailable = 42 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs index 50de497f4..f596c019c 100644 --- a/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttPubAckReasonCode.cs @@ -2,23 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttPubAckReasonCode { - public enum MqttPubAckReasonCode - { - Success = 0, - - /// - /// The message is accepted but there are no subscribers. This is sent only by the Server. If the Server knows that there are no matching subscribers, it MAY use this Reason Code instead of 0x00 (Success). - /// - NoMatchingSubscribers = 16, - - UnspecifiedError = 128, - ImplementationSpecificError = 131, - NotAuthorized = 135, - TopicNameInvalid = 144, - PacketIdentifierInUse = 145, - QuotaExceeded = 151, - PayloadFormatInvalid = 153 - } -} + Success = 0, + + /// + /// The message is accepted but there are no subscribers. This is sent only by the Server. If the Server knows that there are no matching subscribers, it MAY use this Reason Code instead of 0x00 (Success). + /// + NoMatchingSubscribers = 16, + + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicNameInvalid = 144, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + PayloadFormatInvalid = 153 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs index 464b90a28..a4ec6f687 100644 --- a/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttPubCompReasonCode.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttPubCompReasonCode { - public enum MqttPubCompReasonCode - { - Success = 0, - PacketIdentifierNotFound = 146 - } -} + Success = 0, + PacketIdentifierNotFound = 146 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs index 9299b643c..954990aea 100644 --- a/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttPubRecReasonCode.cs @@ -2,18 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttPubRecReasonCode { - public enum MqttPubRecReasonCode - { - Success = 0, - NoMatchingSubscribers = 16, - UnspecifiedError = 128, - ImplementationSpecificError = 131, - NotAuthorized = 135, - TopicNameInvalid = 144, - PacketIdentifierInUse = 145, - QuotaExceeded = 151, - PayloadFormatInvalid = 153 - } -} + Success = 0, + NoMatchingSubscribers = 16, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicNameInvalid = 144, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + PayloadFormatInvalid = 153 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs b/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs index 5bca4b482..1a49d9834 100644 --- a/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttPubRelReasonCode.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttPubRelReasonCode { - public enum MqttPubRelReasonCode - { - Success = 0, - PacketIdentifierNotFound = 146 - } -} + Success = 0, + PacketIdentifierNotFound = 146 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttQualityOfServiceLevel.cs b/Source/MQTTnet/Protocol/MqttQualityOfServiceLevel.cs index e4134b7a1..ba2260d8f 100644 --- a/Source/MQTTnet/Protocol/MqttQualityOfServiceLevel.cs +++ b/Source/MQTTnet/Protocol/MqttQualityOfServiceLevel.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttQualityOfServiceLevel { - public enum MqttQualityOfServiceLevel - { - AtMostOnce = 0x00, - AtLeastOnce = 0x01, - ExactlyOnce = 0x02 - } -} + AtMostOnce = 0x00, + AtLeastOnce = 0x01, + ExactlyOnce = 0x02 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttRetainHandling.cs b/Source/MQTTnet/Protocol/MqttRetainHandling.cs index 7604ea56d..de7a3ba13 100644 --- a/Source/MQTTnet/Protocol/MqttRetainHandling.cs +++ b/Source/MQTTnet/Protocol/MqttRetainHandling.cs @@ -2,14 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttRetainHandling { - public enum MqttRetainHandling - { - SendAtSubscribe = 0, - - SendAtSubscribeIfNewSubscriptionOnly = 1, - - DoNotSendOnSubscribe = 2 - } -} + SendAtSubscribe = 0, + + SendAtSubscribeIfNewSubscriptionOnly = 1, + + DoNotSendOnSubscribe = 2 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs b/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs index 48cc7f836..71499fecc 100644 --- a/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttSubscribeReasonCode.cs @@ -2,24 +2,23 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttSubscribeReasonCode { - public enum MqttSubscribeReasonCode - { - // Compatible with MQTTv3.1.1. - GrantedQoS0 = 0x00, - GrantedQoS1 = 0x01, - GrantedQoS2 = 0x02, - UnspecifiedError = 0x80, - - // New in MQTTv5. - ImplementationSpecificError = 131, - NotAuthorized = 135, - TopicFilterInvalid = 143, - PacketIdentifierInUse = 145, - QuotaExceeded = 151, - SharedSubscriptionsNotSupported = 158, - SubscriptionIdentifiersNotSupported = 161, - WildcardSubscriptionsNotSupported = 162 - } -} + // Compatible with MQTTv3.1.1. + GrantedQoS0 = 0x00, + GrantedQoS1 = 0x01, + GrantedQoS2 = 0x02, + UnspecifiedError = 0x80, + + // New in MQTTv5. + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicFilterInvalid = 143, + PacketIdentifierInUse = 145, + QuotaExceeded = 151, + SharedSubscriptionsNotSupported = 158, + SubscriptionIdentifiersNotSupported = 161, + WildcardSubscriptionsNotSupported = 162 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttSubscribeReturnCode.cs b/Source/MQTTnet/Protocol/MqttSubscribeReturnCode.cs index da569b6a2..34ed91379 100644 --- a/Source/MQTTnet/Protocol/MqttSubscribeReturnCode.cs +++ b/Source/MQTTnet/Protocol/MqttSubscribeReturnCode.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttSubscribeReturnCode { - public enum MqttSubscribeReturnCode - { - SuccessMaximumQoS0 = 0x00, - SuccessMaximumQoS1 = 0x01, - SuccessMaximumQoS2 = 0x02, - Failure = 0x80 - } -} + SuccessMaximumQoS0 = 0x00, + SuccessMaximumQoS1 = 0x01, + SuccessMaximumQoS2 = 0x02, + Failure = 0x80 +} \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttTopicValidator.cs b/Source/MQTTnet/Protocol/MqttTopicValidator.cs index efdeeafaf..32baef712 100644 --- a/Source/MQTTnet/Protocol/MqttTopicValidator.cs +++ b/Source/MQTTnet/Protocol/MqttTopicValidator.cs @@ -5,53 +5,52 @@ using System; using MQTTnet.Exceptions; -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public static class MqttTopicValidator { - public static class MqttTopicValidator + public static void ThrowIfInvalid(MqttApplicationMessage applicationMessage) { - public static void ThrowIfInvalid(MqttApplicationMessage applicationMessage) + ArgumentNullException.ThrowIfNull(applicationMessage); + + if (applicationMessage.TopicAlias == 0) { - ArgumentNullException.ThrowIfNull(applicationMessage); + ThrowIfInvalid(applicationMessage.Topic); + } + } - if (applicationMessage.TopicAlias == 0) - { - ThrowIfInvalid(applicationMessage.Topic); - } + public static void ThrowIfInvalid(string topic) + { + if (string.IsNullOrEmpty(topic)) + { + throw new MqttProtocolViolationException("Topic should not be empty."); } - public static void ThrowIfInvalid(string topic) + foreach (var @char in topic) { - if (string.IsNullOrEmpty(topic)) + if (@char == '+') { - throw new MqttProtocolViolationException("Topic should not be empty."); + throw new MqttProtocolViolationException("The character '+' is not allowed in topics."); } - foreach (var @char in topic) + if (@char == '#') { - if (@char == '+') - { - throw new MqttProtocolViolationException("The character '+' is not allowed in topics."); - } - - if (@char == '#') - { - throw new MqttProtocolViolationException("The character '#' is not allowed in topics."); - } + throw new MqttProtocolViolationException("The character '#' is not allowed in topics."); } } + } - public static void ThrowIfInvalidSubscribe(string topic) + public static void ThrowIfInvalidSubscribe(string topic) + { + if (string.IsNullOrEmpty(topic)) { - if (string.IsNullOrEmpty(topic)) - { - throw new MqttProtocolViolationException("Topic should not be empty."); - } + throw new MqttProtocolViolationException("Topic should not be empty."); + } - var indexOfHash = topic.IndexOf("#", StringComparison.Ordinal); - if (indexOfHash != -1 && indexOfHash != topic.Length - 1) - { - throw new MqttProtocolViolationException("The character '#' is only allowed as last character"); - } + var indexOfHash = topic.IndexOf('#', StringComparison.Ordinal); + if (indexOfHash != -1 && indexOfHash != topic.Length - 1) + { + throw new MqttProtocolViolationException("The character '#' is only allowed as last character"); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs b/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs index cd85c8edd..957282b5d 100644 --- a/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs +++ b/Source/MQTTnet/Protocol/MqttUnsubscribeReasonCode.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MQTTnet.Protocol +namespace MQTTnet.Protocol; + +public enum MqttUnsubscribeReasonCode { - public enum MqttUnsubscribeReasonCode - { - Success = 0, - NoSubscriptionExisted = 17, - UnspecifiedError = 128, - ImplementationSpecificError = 131, - NotAuthorized = 135, - TopicFilterInvalid = 143, - PacketIdentifierInUse = 145 - } -} + Success = 0, + NoSubscriptionExisted = 17, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicFilterInvalid = 143, + PacketIdentifierInUse = 145 +} \ No newline at end of file diff --git a/Source/MQTTnet/Publishing/MqttClientPublishResult.cs b/Source/MQTTnet/Publishing/MqttClientPublishResult.cs index 89fd676af..c85a1a3cf 100644 --- a/Source/MQTTnet/Publishing/MqttClientPublishResult.cs +++ b/Source/MQTTnet/Publishing/MqttClientPublishResult.cs @@ -23,7 +23,7 @@ public MqttClientPublishResult(ushort? packetIdentifier, MqttClientPublishReason /// topic but overall transmit /// to the server etc. was a success. /// - public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers; + public bool IsSuccess => ReasonCode is MqttClientPublishReasonCode.Success or MqttClientPublishReasonCode.NoMatchingSubscribers; /// /// Gets the packet identifier which was used for this publish. diff --git a/Source/MQTTnet/Publishing/MqttClientPublishResultFactory.cs b/Source/MQTTnet/Publishing/MqttClientPublishResultFactory.cs index 4109cc6f0..7a947ef96 100644 --- a/Source/MQTTnet/Publishing/MqttClientPublishResultFactory.cs +++ b/Source/MQTTnet/Publishing/MqttClientPublishResultFactory.cs @@ -10,10 +10,10 @@ namespace MQTTnet; public sealed class MqttClientPublishResultFactory { - static readonly IReadOnlyCollection EmptyUserProperties = new List(); + static readonly IReadOnlyCollection EmptyUserProperties = []; static readonly MqttClientPublishResult AtMostOnceSuccessResult = new(null, MqttClientPublishReasonCode.Success, null, EmptyUserProperties); - public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket) + public static MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket) { // QoS 0 has no response. So we treat it as a success always. if (pubAckPacket == null) @@ -31,7 +31,7 @@ public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket) return result; } - public MqttClientPublishResult Create(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket) + public static MqttClientPublishResult Create(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket) { if (pubRecPacket == null || pubCompPacket == null) { diff --git a/Source/MQTTnet/Receiving/MqttApplicationMessageReceivedEventArgs.cs b/Source/MQTTnet/Receiving/MqttApplicationMessageReceivedEventArgs.cs index e323bdc3b..d1a2c4998 100644 --- a/Source/MQTTnet/Receiving/MqttApplicationMessageReceivedEventArgs.cs +++ b/Source/MQTTnet/Receiving/MqttApplicationMessageReceivedEventArgs.cs @@ -71,7 +71,7 @@ public MqttApplicationMessageReceivedEventArgs( /// /// Gets or sets the user properties which will be sent to the server in the ACK packet etc. /// - public List ResponseUserProperties { get; } = new(); + public List ResponseUserProperties { get; } = []; public object Tag { get; set; } diff --git a/Source/MQTTnet/Subscribing/MqttClientSubscribeOptions.cs b/Source/MQTTnet/Subscribing/MqttClientSubscribeOptions.cs index b26eb01d0..2b1434a6e 100644 --- a/Source/MQTTnet/Subscribing/MqttClientSubscribeOptions.cs +++ b/Source/MQTTnet/Subscribing/MqttClientSubscribeOptions.cs @@ -24,7 +24,7 @@ public sealed class MqttClientSubscribeOptions /// Gets or sets a list of topic filters the client wants to subscribe to. /// Topic filters can include regular topics or wild cards. /// - public List TopicFilters { get; set; } = new(); + public List TopicFilters { get; set; } = []; /// /// Gets or sets the user properties. diff --git a/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsBuilder.cs b/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsBuilder.cs index 486b86cc8..0fdd3f239 100644 --- a/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsBuilder.cs +++ b/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsBuilder.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -69,11 +68,7 @@ public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFi { ArgumentNullException.ThrowIfNull(topicFilter); - if (_subscribeOptions.TopicFilters == null) - { - _subscribeOptions.TopicFilters = new List(); - } - + _subscribeOptions.TopicFilters ??= []; _subscribeOptions.TopicFilters.Add(topicFilter); return this; @@ -85,11 +80,7 @@ public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFi /// public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value) { - if (_subscribeOptions.UserProperties == null) - { - _subscribeOptions.UserProperties = new List(); - } - + _subscribeOptions.UserProperties ??= []; _subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value)); return this; diff --git a/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsValidator.cs b/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsValidator.cs index c490642b5..801539603 100644 --- a/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsValidator.cs +++ b/Source/MQTTnet/Subscribing/MqttClientSubscribeOptionsValidator.cs @@ -21,7 +21,7 @@ public static void ThrowIfNotSupported(MqttClientSubscribeOptions options, MqttP return; } - if (options.UserProperties?.Any() == true) + if (options.UserProperties?.Count > 0) { Throw(nameof(options.UserProperties)); } diff --git a/Source/MQTTnet/Subscribing/MqttClientSubscribeResultFactory.cs b/Source/MQTTnet/Subscribing/MqttClientSubscribeResultFactory.cs index b7ec5fde5..e21636c11 100644 --- a/Source/MQTTnet/Subscribing/MqttClientSubscribeResultFactory.cs +++ b/Source/MQTTnet/Subscribing/MqttClientSubscribeResultFactory.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using MQTTnet.Exceptions; using MQTTnet.Packets; @@ -12,15 +11,15 @@ namespace MQTTnet; public sealed class MqttClientSubscribeResultFactory { - static readonly IReadOnlyCollection EmptyUserProperties = new List(); + static readonly IReadOnlyCollection EmptyUserProperties = []; - public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) + public static MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) { ArgumentNullException.ThrowIfNull(subscribePacket); ArgumentNullException.ThrowIfNull(subAckPacket); // MQTTv5.0.0 handling. - if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count) + if (subAckPacket.ReasonCodes.Count != 0 && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count) { throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1]."); } diff --git a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptions.cs b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptions.cs index 3c0f61bcf..f2c0e7bac 100644 --- a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptions.cs +++ b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptions.cs @@ -13,7 +13,7 @@ public sealed class MqttClientUnsubscribeOptions /// Gets or sets a list of topic filters the client wants to unsubscribe from. /// Topic filters can include regular topics or wild cards. /// - public List TopicFilters { get; set; } = new(); + public List TopicFilters { get; set; } = []; /// /// Gets or sets the user properties. diff --git a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs index c17f30810..f77fc49df 100644 --- a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs +++ b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using MQTTnet.Packets; namespace MQTTnet; @@ -21,11 +20,7 @@ public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic) { ArgumentNullException.ThrowIfNull(topic); - if (_unsubscribeOptions.TopicFilters is null) - { - _unsubscribeOptions.TopicFilters = new List(); - } - + _unsubscribeOptions.TopicFilters ??= []; _unsubscribeOptions.TopicFilters.Add(topic); return this; @@ -55,10 +50,7 @@ public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty use { ArgumentNullException.ThrowIfNull(userProperty); - if (_unsubscribeOptions.UserProperties is null) - { - _unsubscribeOptions.UserProperties = new List(); - } + _unsubscribeOptions.UserProperties ??= []; _unsubscribeOptions.UserProperties.Add(userProperty); diff --git a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs index 78fbb8eda..11fb92718 100644 --- a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs +++ b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using MQTTnet.Formatter; namespace MQTTnet; @@ -20,7 +19,7 @@ public static void ThrowIfNotSupported(MqttClientUnsubscribeOptions options, Mqt return; } - if (options.UserProperties?.Any() == true) + if (options.UserProperties?.Count > 0) { Throw(nameof(options.UserProperties)); } diff --git a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeResultFactory.cs b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeResultFactory.cs index 202460e3e..c18ba3fa1 100644 --- a/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeResultFactory.cs +++ b/Source/MQTTnet/Unsubscribing/MqttClientUnsubscribeResultFactory.cs @@ -11,9 +11,9 @@ namespace MQTTnet; public sealed class MqttClientUnsubscribeResultFactory { - static readonly IReadOnlyCollection EmptyUserProperties = new List(); + static readonly IReadOnlyCollection EmptyUserProperties = []; - public MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) + public static MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) { ArgumentNullException.ThrowIfNull(unsubscribePacket); ArgumentNullException.ThrowIfNull(unsubAckPacket);