From 4adfca16245d2c17931aa931e32e58ff53293b01 Mon Sep 17 00:00:00 2001 From: christian <6939810+chkr1011@users.noreply.github.com> Date: Wed, 15 May 2024 17:22:55 +0200 Subject: [PATCH] Fix unit tests Refactor code --- .editorconfig | 8 +- .github/workflows/ci.yml | 6 +- README.md | 17 +- Samples/MQTTnet.Samples.csproj | 10 +- Samples/Server/Server_Diagnostics_Samples.cs | 1 - Samples/Server/Server_Simple_Samples.cs | 2 +- .../MQTTnet.AspTestApp.csproj | 8 +- .../ApplicationBuilderExtensions.cs | 33 +- .../AspNetMqttServerOptionsBuilder.cs | 19 +- .../MQTTnet.AspNetCore.csproj | 8 +- .../MqttConnectionHandler.cs | 72 +- Source/MQTTnet.AspnetCore/MqttHostedServer.cs | 43 +- .../MqttWebSocketServerAdapter.cs | 74 +- .../ServiceCollectionExtensions.cs | 2 +- .../MQTTnet.Benchmarks.csproj | 18 +- .../MessageDeliveryBenchmark.cs | 340 ++-- .../MessageProcessingBenchmark.cs | 63 +- .../MqttTcpChannelBenchmark.cs | 114 +- Source/MQTTnet.Benchmarks/Program.cs | 4 +- .../ReaderExtensionsBenchmark.cs | 2 +- .../MQTTnet.Benchmarks/SubscribeBenchmark.cs | 84 +- .../UnsubscribeBenchmark.cs | 79 +- .../MQTTnet.Extensions.ManagedClient.csproj | 4 +- .../ManagedMqttClientOptions.cs | 32 +- .../MQTTnet.Extensions.Rpc.csproj | 4 +- .../MQTTnet.Extensions.Rpc.csproj.DotSettings | 10 +- .../MQTTnet.Extensions.TopicTemplate.csproj | 10 +- .../MqttServerClientDisconnectOptions.cs | 3 +- ...qttServerClientDisconnectOptionsBuilder.cs | 3 +- .../Events/PreparingSessionEventArgs.cs | 1 + .../{Adapter => }/IMqttServerAdapter.cs | 2 +- .../InjectedMqttApplicationMessage.cs | 30 +- .../Adapter/MqttTcpServerAdapter.cs | 2 +- .../Adapter/MqttTcpServerListener.cs | 2 +- .../Internal/CheckSubscriptionsResult.cs | 3 +- .../DispatchApplicationMessageResult.cs | 3 +- .../Internal/EnqueueDataPacketResult.cs | 2 +- .../Formatter/MqttConnAckPacketFactory.cs | 2 +- .../Formatter/MqttDisconnectPacketFactory.cs | 3 +- .../Formatter/MqttPubAckPacketFactory.cs | 2 +- .../Formatter/MqttPubCompPacketFactory.cs | 26 + .../Formatter/MqttPubRecPacketFactory.cs | 2 +- .../Formatter/MqttPubRelPacketFactory.cs | 26 + .../Formatter/MqttPublishPacketFactory.cs | 38 +- .../Formatter/MqttSubAckPacketFactory.cs | 2 +- .../Formatter/MqttUnsubAckPacketFactory.cs | 2 +- .../ISubscriptionChangedNotification.cs | 4 +- Source/MQTTnet.Server/Internal/MqttClient.cs | 833 +++++---- .../Internal/MqttClientSessionsManager.cs | 1061 ++++++----- .../Internal/MqttClientStatistics.cs | 4 +- .../MqttClientSubscriptionsManager.cs | 8 +- .../Internal/MqttRetainedMessagesManager.cs | 6 +- .../Internal/MqttServerEventContainer.cs | 3 +- .../Internal/MqttServerKeepAliveMonitor.cs | 10 +- Source/MQTTnet.Server/Internal/MqttSession.cs | 336 ++-- .../Internal/MqttSessionsStorage.cs | 5 +- .../Internal/MqttSubscription.cs | 2 +- .../MQTTnet.Server/Internal/MqttTopicHash.cs | 2 +- .../Internal/SubscribeResult.cs | 3 +- .../Internal/TopicHashMaskSubscriptions.cs | 4 +- .../Internal/UnsubscribeResult.cs | 3 +- Source/MQTTnet.Server/MQTTnet.Server.csproj | 4 +- .../MQTTnet.Server.csproj.DotSettings | 12 + .../MqttClientDisconnectType.cs | 15 +- .../MqttRetainedMessageMatch.cs | 20 +- Source/MQTTnet.Server/MqttServer.cs | 641 ++++--- Source/MQTTnet.Server/MqttServerExtensions.cs | 162 +- Source/MQTTnet.Server/MqttServerFactory.cs | 3 +- Source/MQTTnet.Server/PublishResponse.cs | 16 +- .../MQTTnet.Server/Status/MqttClientStatus.cs | 77 +- .../Status/MqttClientStatusExtensions.cs | 34 +- .../Status/MqttSessionStatus.cs | 80 +- .../Stopping/MqttServerStopOptions.cs | 1 - .../Stopping/MqttServerStopOptionsBuilder.cs | 1 - Source/MQTTnet.Server/SubscribeResponse.cs | 28 +- Source/MQTTnet.Server/UnsubscribeResponse.cs | 26 +- Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj | 8 +- .../ASP/Mockups/ConnectionHandlerMockup.cs | 54 +- Source/MQTTnet.Tests/MQTTnet.Tests.csproj | 22 +- .../MQTTnet.Tests/Mockups/TestEnvironment.cs | 2 +- Source/MQTTnet.Tests/Server/General.cs | 7 +- .../MqttRetainedMessageManager_Tests.cs | 5 +- .../Server/MqttSubscriptionsManager_Tests.cs | 1 + Source/MQTTnet.Tests/Server/QoS_Tests.cs | 239 ++- Source/MQTTnet.Tests/Server/Status_Tests.cs | 227 ++- .../Server/Subscription_TopicHash_Tests.cs | 1 + .../TopicFilterComparer_Tests.cs | 1 + Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 32 +- .../Adapter/IMqttClientAdapterFactory.cs | 11 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 669 ++++--- .../Adapter/MqttConnectingFailedException.cs | 20 +- Source/MQTTnet/Adapter/MqttPacketInspector.cs | 121 +- Source/MQTTnet/Adapter/ReceivedMqttPacket.cs | 29 +- Source/MQTTnet/Channel/IMqttChannel.cs | 34 +- .../Connecting/MqttClientConnectResult.cs | 223 +-- .../Connecting/MqttClientConnectResultCode.cs | 53 +- .../MqttClientConnectResultFactory.cs | 168 +- .../MqttClientConnectedEventArgs.cs | 23 +- .../MqttClientConnectingEventArgs.cs | 15 +- .../MqttClientDisconnectOptions.cs | 47 +- .../MqttClientDisconnectOptionsBuilder.cs | 87 +- .../MqttClientDisconnectOptionsReason.cs | 41 +- .../MqttClientDisconnectOptionsValidator.cs | 45 +- .../MqttClientDisconnectReason.cs | 69 +- .../MqttClientDisconnectedEventArgs.cs | 73 +- .../MqttClientDisconnectedException.cs | 9 +- ...ntUnexpectedDisconnectReceivedException.cs | 35 +- ...ttExtendedAuthenticationExchangeHandler.cs | 11 +- ...ttExtendedAuthenticationExchangeContext.cs | 98 +- .../MqttExtendedAuthenticationExchangeData.cs | 63 +- Source/MQTTnet/Client/IMqttClient.cs | 37 +- .../Client/Internal/MqttClientEvents.cs | 17 +- .../Internal/MqttClientResultFactory.cs | 15 +- Source/MQTTnet/Client/MqttClient.cs | 1571 ++++++++--------- .../Client/MqttClientConnectionStatus.cs | 15 +- Source/MQTTnet/Client/MqttClientExtensions.cs | 277 ++- .../Client/MqttPacketIdentifierProvider.cs | 43 +- .../DefaultMqttCertificatesProvider.cs | 37 +- .../IMqttClientCertificatesProvider.cs | 11 +- .../Options/IMqttClientChannelOptions.cs | 11 +- .../Options/IMqttClientCredentialsProvider.cs | 12 +- ...MqttClientCertificateSelectionEventArgs.cs | 41 +- ...qttClientCertificateValidationEventArgs.cs | 27 +- .../Client/Options/MqttClientCredentials.cs | 37 +- ...ientDefaultCertificateValidationHandler.cs | 39 +- .../Options/MqttClientOptionsValidator.cs | 199 ++- .../Client/Options/MqttClientTcpOptions.cs | 85 +- .../Client/Options/MqttClientTlsOptions.cs | 55 +- .../Options/MqttClientTlsOptionsBuilder.cs | 211 ++- .../Options/MqttClientWebSocketOptions.cs | 47 +- .../MqttClientWebSocketOptionsBuilder.cs | 115 +- .../MqttClientWebSocketProxyOptions.cs | 21 +- .../MqttClientWebSocketProxyOptionsBuilder.cs | 109 +- .../Publishing/MqttClientPublishReasonCode.cs | 29 +- .../Publishing/MqttClientPublishResult.cs | 89 +- .../MqttClientPublishResultFactory.cs | 87 +- ...MqttApplicationMessageReceivedEventArgs.cs | 129 +- ...qttApplicationMessageReceivedReasonCode.cs | 27 +- .../Subscribing/MqttClientSubscribeOptions.cs | 57 +- .../MqttClientSubscribeOptionsBuilder.cs | 139 +- .../MqttClientSubscribeOptionsValidator.cs | 81 +- .../Subscribing/MqttClientSubscribeResult.cs | 63 +- .../MqttClientSubscribeResultCode.cs | 35 +- .../MqttClientSubscribeResultFactory.cs | 59 +- .../MqttClientSubscribeResultItem.cs | 37 +- .../MqttClientUnsubscribeOptions.cs | 37 +- .../MqttClientUnsubscribeOptionsBuilder.cs | 93 +- .../MqttClientUnsubscribeOptionsValidator.cs | 37 +- .../MqttClientUnsubscribeResult.cs | 63 +- .../MqttClientUnsubscribeResultCode.cs | 23 +- .../MqttClientUnsubscribeResultFactory.cs | 69 +- .../MqttClientUnsubscribeResultItem.cs | 35 +- .../Formatter/MqttConnectPacketFactory.cs | 85 +- .../Formatter/MqttDisconnectPacketFactory.cs | 86 +- .../MQTTnet/Formatter/MqttPacketFactories.cs | 27 - .../Formatter/MqttPubAckPacketFactory.cs | 31 +- .../Formatter/MqttPubCompPacketFactory.cs | 25 +- .../Formatter/MqttPubRecPacketFactory.cs | 37 +- .../Formatter/MqttPubRelPacketFactory.cs | 25 +- .../Formatter/MqttPublishPacketFactory.cs | 52 +- .../Formatter/MqttSubscribePacketFactory.cs | 29 +- .../Formatter/MqttUnsubscribePacketFactory.cs | 33 +- Source/MQTTnet/MQTTnet.csproj | 4 +- Source/MQTTnet/MQTTnet.csproj.DotSettings | 64 +- .../MqttApplicationMessageExtensions.cs | 37 +- Source/MQTTnet/Packets/MqttAuthPacket.cs | 21 +- Source/MQTTnet/Packets/MqttConnAckPacket.cs | 75 +- Source/MQTTnet/Packets/MqttPacket.cs | 9 +- .../MQTTnet/Packets/MqttPacketExtensions.cs | 177 +- Source/MQTTnet/Packets/MqttPingRespPacket.cs | 19 +- Source/MQTTnet/Packets/MqttPubAckPacket.cs | 37 +- Source/MQTTnet/Packets/MqttPubCompPacket.cs | 37 +- Source/MQTTnet/Packets/MqttPublishPacket.cs | 41 +- Source/MQTTnet/Packets/MqttSubscribePacket.cs | 33 +- .../MQTTnet/Packets/MqttUnsubscribePacket.cs | 25 +- Source/MQTTnet/Packets/MqttUserProperty.cs | 61 +- 176 files changed, 6165 insertions(+), 6294 deletions(-) rename Source/MQTTnet.Server/{Adapter => }/IMqttServerAdapter.cs (93%) rename Source/MQTTnet.Server/{ => Internal}/Adapter/MqttTcpServerAdapter.cs (98%) rename Source/MQTTnet.Server/{ => Internal}/Adapter/MqttTcpServerListener.cs (99%) rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttConnAckPacketFactory.cs (97%) rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttDisconnectPacketFactory.cs (94%) rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttPubAckPacketFactory.cs (96%) create mode 100644 Source/MQTTnet.Server/Internal/Formatter/MqttPubCompPacketFactory.cs rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttPubRecPacketFactory.cs (95%) create mode 100644 Source/MQTTnet.Server/Internal/Formatter/MqttPubRelPacketFactory.cs rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttPublishPacketFactory.cs (59%) rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttSubAckPacketFactory.cs (95%) rename Source/MQTTnet.Server/{ => Internal}/Formatter/MqttUnsubAckPacketFactory.cs (95%) create mode 100644 Source/MQTTnet.Server/MQTTnet.Server.csproj.DotSettings delete mode 100644 Source/MQTTnet/Formatter/MqttPacketFactories.cs diff --git a/.editorconfig b/.editorconfig index da3f0a4de..46144365e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,14 +21,14 @@ dotnet_naming_rule.static_readonly_rule.symbols = static_readonly_symbols dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_style.lower_camel_case_style.required_prefix = _ dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case -dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.constants_symbols.applicable_kinds = field dotnet_naming_symbols.constants_symbols.required_modifiers = const -dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field -dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field -dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly +dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a826e9a4..23bbcf213 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,13 +32,13 @@ jobs: - name: Build solution run: dotnet build MQTTnet.sln --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}/certificate.snk - - name: Collect nuget Packages + - name: Collect nuget packages uses: actions/upload-artifact@v2 with: name: nuget Packages path: | - **\*.nupkg - **\*.snupkg + **/*.nupkg + **/*.snupkg - name: Execute tests run: dotnet test --no-restore --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj diff --git a/README.md b/README.md index 90c25b1ea..70bfe37a7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ # MQTTnet -MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture. +MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server ( +broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework +version and CPU architecture. ## Features @@ -28,13 +30,15 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Unit tested (~636 tests) * No external dependencies -\* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. +\* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP +channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. ### Client * Communication via TCP (+TLS) or WS (WebSocket) supported * Included core _LowLevelMqttClient_ with low level functionality -* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application messages are queued and re-scheduled for higher QoS levels automatically. +* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application + messages are queued and re-scheduled for higher QoS levels automatically. * Rx support (via another project) * Compatible with Microsoft Azure IoT Hub @@ -52,9 +56,11 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov ## Getting Started -MQTTnet is delivered via NuGet package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/ +MQTTnet is delivered via NuGet package manager. You can find the packages +here: https://www.nuget.org/packages/MQTTnet/ Use these command in the Package Manager console to install MQTTnet manually: + ``` Install-Package MQTTnet ``` @@ -68,7 +74,8 @@ Samples for using MQTTnet are part of this repository. For starters these sample ## Code of Conduct -This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. +This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our +community. For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). ## .NET Foundation diff --git a/Samples/MQTTnet.Samples.csproj b/Samples/MQTTnet.Samples.csproj index bfc3a9408..90640f8b6 100644 --- a/Samples/MQTTnet.Samples.csproj +++ b/Samples/MQTTnet.Samples.csproj @@ -14,11 +14,11 @@ - - - - - + + + + + diff --git a/Samples/Server/Server_Diagnostics_Samples.cs b/Samples/Server/Server_Diagnostics_Samples.cs index b72fec3d0..07bc8e0b6 100644 --- a/Samples/Server/Server_Diagnostics_Samples.cs +++ b/Samples/Server/Server_Diagnostics_Samples.cs @@ -13,7 +13,6 @@ namespace MQTTnet.Samples.Server; // ReSharper disable InconsistentNaming // ReSharper disable EmptyConstructor // ReSharper disable MemberCanBeMadeStatic.Local - public static class Server_Diagnostics_Samples { public static async Task Get_Notified_When_Client_Received_Message() diff --git a/Samples/Server/Server_Simple_Samples.cs b/Samples/Server/Server_Simple_Samples.cs index 4abededb0..bfb86f3b0 100644 --- a/Samples/Server/Server_Simple_Samples.cs +++ b/Samples/Server/Server_Simple_Samples.cs @@ -216,4 +216,4 @@ public void Publish(MqttNetLogLevel logLevel, string source, string message, obj } } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj b/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj index a624c945a..f977d5cd0 100644 --- a/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj +++ b/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj @@ -12,13 +12,13 @@ - - - + + + - + diff --git a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs index d7bc3e3d7..f98983da9 100644 --- a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -7,15 +7,17 @@ using Microsoft.Extensions.DependencyInjection; using MQTTnet.Server; -namespace MQTTnet.AspNetCore +namespace MQTTnet.AspNetCore; + +public static class ApplicationBuilderExtensions { - public static class ApplicationBuilderExtensions + [Obsolete( + "This class is obsolete and will be removed in a future version. The recommended alternative is to use MapMqtt inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] + public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt") { - [Obsolete("This class is obsolete and will be removed in a future version. The recommended alternative is to use MapMqtt inside Microsoft.AspNetCore.Builder.UseEndpoints(...).")] - public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt") - { - app.UseWebSockets(); - app.Use(async (context, next) => + app.UseWebSockets(); + app.Use( + async (context, next) => { if (!context.WebSockets.IsWebSocketRequest || context.Request.Path != path) { @@ -37,16 +39,15 @@ public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, } }); - return app; - } + return app; + } - public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action configure) - { - var server = app.ApplicationServices.GetRequiredService(); + public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action configure) + { + var server = app.ApplicationServices.GetRequiredService(); - configure(server); + configure(server); - return app; - } + return app; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.cs b/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.cs index a95e0017a..394483959 100644 --- a/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.cs +++ b/Source/MQTTnet.AspnetCore/AspNetMqttServerOptionsBuilder.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. -using MQTTnet.Server; using System; +using MQTTnet.Server; + +namespace MQTTnet.AspNetCore; -namespace MQTTnet.AspNetCore +public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder { - public sealed class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder + public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider) { - public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public IServiceProvider ServiceProvider { get; } + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } -} + + public IServiceProvider ServiceProvider { get; } +} \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj index dad8b6624..cf8e58740 100644 --- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj +++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj @@ -42,16 +42,16 @@ - - + + - - + + diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index 398e2902d..4e1138a40 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -2,58 +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; +using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using MQTTnet.Adapter; -using MQTTnet.Server; -using System; -using System.Threading.Tasks; using MQTTnet.Diagnostics; using MQTTnet.Formatter; -using MQTTnet.Server.Adapter; +using MQTTnet.Server; + +namespace MQTTnet.AspNetCore; -namespace MQTTnet.AspNetCore +public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter { - public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter - { - MqttServerOptions _serverOptions; + MqttServerOptions _serverOptions; + + public Func ClientHandler { get; set; } - public Func ClientHandler { get; set; } + public void Dispose() + { + } - public override async Task OnConnectedAsync(ConnectionContext connection) + public override async Task OnConnectedAsync(ConnectionContext connection) + { + // required for websocket transport to work + var transferFormatFeature = connection.Features.Get(); + if (transferFormatFeature != null) { - // required for websocket transport to work - var transferFormatFeature = connection.Features.Get(); - if (transferFormatFeature != null) - { - transferFormatFeature.ActiveFormat = TransferFormat.Binary; - } + transferFormatFeature.ActiveFormat = TransferFormat.Binary; + } - var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax)); - using (var adapter = new MqttConnectionContext(formatter, connection)) + var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax)); + 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); } } + } - public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) - { - _serverOptions = options; - - return Task.CompletedTask; - } + public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) + { + _serverOptions = options; - public Task StopAsync() - { - return Task.CompletedTask; - } + return Task.CompletedTask; + } - public void Dispose() - { - } + public Task StopAsync() + { + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs index e98be27c2..cb6e5fb5c 100644 --- a/Source/MQTTnet.AspnetCore/MqttHostedServer.cs +++ b/Source/MQTTnet.AspnetCore/MqttHostedServer.cs @@ -7,36 +7,33 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; -using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Server; -using MQTTnet.Server.Adapter; -namespace MQTTnet.AspNetCore +namespace MQTTnet.AspNetCore; + +public sealed class MqttHostedServer : MqttServer, IHostedService { - public sealed class MqttHostedServer : MqttServer, IHostedService - { - readonly MqttServerFactory _mqttServerFactory; + readonly MqttServerFactory _mqttServerFactory; - public MqttHostedServer(MqttServerFactory mqttServerFactory, MqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) : base( - options, - adapters, - logger) - { - _mqttServerFactory = mqttServerFactory ?? throw new ArgumentNullException(nameof(mqttServerFactory)); - } + public MqttHostedServer(MqttServerFactory mqttServerFactory, MqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) : base( + options, + adapters, + logger) + { + _mqttServerFactory = mqttServerFactory ?? throw new ArgumentNullException(nameof(mqttServerFactory)); + } - public async Task StartAsync(CancellationToken cancellationToken) - { - // The yield makes sure that the hosted service is considered up and running. - await Task.Yield(); + public async Task StartAsync(CancellationToken cancellationToken) + { + // The yield makes sure that the hosted service is considered up and running. + await Task.Yield(); - _ = StartAsync(); - } + _ = StartAsync(); + } - public Task StopAsync(CancellationToken cancellationToken) - { - return StopAsync(_mqttServerFactory.CreateMqttServerStopOptionsBuilder().Build()); - } + public Task StopAsync(CancellationToken cancellationToken) + { + return StopAsync(_mqttServerFactory.CreateMqttServerStopOptionsBuilder().Build()); } } \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 2d01a9fcf..0d9522949 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -11,61 +11,59 @@ using MQTTnet.Formatter; using MQTTnet.Implementations; using MQTTnet.Server; -using MQTTnet.Server.Adapter; -namespace MQTTnet.AspNetCore +namespace MQTTnet.AspNetCore; + +public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter { - public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter - { - IMqttNetLogger _logger = MqttNetNullLogger.Instance; + IMqttNetLogger _logger = MqttNetNullLogger.Instance; + + public Func ClientHandler { get; set; } - public Func ClientHandler { get; set; } + public void Dispose() + { + } - public void Dispose() + public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) + { + if (webSocket == null) { + throw new ArgumentNullException(nameof(webSocket)); } - public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) - { - if (webSocket == null) - { - throw new ArgumentNullException(nameof(webSocket)); - } + var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; - var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; + var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false); + try + { + var isSecureConnection = clientCertificate != null; - var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false); - try + var clientHandler = ClientHandler; + if (clientHandler != null) { - var isSecureConnection = clientCertificate != null; + var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); + var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate); - var clientHandler = ClientHandler; - if (clientHandler != null) + using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger)) { - var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); - var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate); - - using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger)) - { - await clientHandler(channelAdapter).ConfigureAwait(false); - } + await clientHandler(channelAdapter).ConfigureAwait(false); } } - finally - { - clientCertificate?.Dispose(); - } } - - public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) + finally { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - return Task.CompletedTask; + clientCertificate?.Dispose(); } + } - public Task StopAsync() - { - return Task.CompletedTask; - } + public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + return Task.CompletedTask; + } + + public Task StopAsync() + { + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs index 17ca79272..27b55161e 100644 --- a/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Hosting; using MQTTnet.Diagnostics; using MQTTnet.Server; -using MQTTnet.Server.Adapter; +using MQTTnet.Server.Internal.Adapter; namespace MQTTnet.AspNetCore; diff --git a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj index 20fdf0e0a..e43adc753 100644 --- a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj +++ b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj @@ -12,15 +12,23 @@ 1591;NETSDK1138 + + 1591;NETSDK1138 + + + + 1591;NETSDK1138 + + - + - - - - + + + + \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs index b32fc7922..6505abcf7 100644 --- a/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs @@ -2,233 +2,217 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using BenchmarkDotNet.Attributes; -using MQTTnet.Client; -using MQTTnet.Server; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using MQTTnet.Client; +using MQTTnet.Packets; +using MQTTnet.Server; -namespace MQTTnet.Benchmarks -{ - [MemoryDiagnoser] - public class MessageDeliveryBenchmark : BaseBenchmark - { - List _topicPublishMessages; +namespace MQTTnet.Benchmarks; - [Params(1, 5)] - public int NumTopicsPerPublisher; +[MemoryDiagnoser] +public class MessageDeliveryBenchmark : BaseBenchmark +{ + List _allSubscribedTopics; // Keep track of the subset of topics that are subscribed + CancellationTokenSource _cancellationTokenSource; - [Params(1000, 10000)] - public int NumPublishers; + object _lockMsgCount; + int _messagesExpectedCount; + int _messagesReceivedCount; + Dictionary _mqttPublisherClientsByPublisherName; - [Params(10)] - public int NumSubscribers; + MqttServer _mqttServer; + List _mqttSubscriberClients; + Dictionary _publisherByTopic; + List _topicPublishMessages; - [Params(5, 10, 20, 50)] - public int NumSubscribedTopicsPerSubscriber; + Dictionary> _topicsByPublisher; - object _lockMsgCount; - int _messagesReceivedCount; - int _messagesExpectedCount; - CancellationTokenSource _cancellationTokenSource; + [Params(1000, 10000)] public int NumPublishers; - MqttServer _mqttServer; - List _mqttSubscriberClients; - Dictionary _mqttPublisherClientsByPublisherName; + [Params(5, 10, 20, 50)] public int NumSubscribedTopicsPerSubscriber; - Dictionary> _topicsByPublisher; - Dictionary _publisherByTopic; - List _allSubscribedTopics; // Keep track of the subset of topics that are subscribed + [Params(10)] public int NumSubscribers; + [Params(1, 5)] public int NumTopicsPerPublisher; - [GlobalSetup] - public void Setup() + [GlobalCleanup] + public void Cleanup() + { + foreach (var mp in _mqttPublisherClientsByPublisherName) { - _lockMsgCount = new object(); + var mqttPublisherClient = mp.Value; + mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult(); + mqttPublisherClient.Dispose(); + } - Dictionary> singleWildcardTopicsByPublisher; - Dictionary> multiWildcardTopicsByPublisher; + _mqttPublisherClientsByPublisherName.Clear(); - TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher); + foreach (var mqttSubscriber in _mqttSubscriberClients) + { + mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult(); + mqttSubscriber.Dispose(); + } - 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); - } + _mqttSubscriberClients.Clear(); - // Create server - var serverFactory = new MqttServerFactory(); - var clientFactory = new MqttClientFactory(); - var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - _mqttServer = serverFactory.CreateMqttServer(serverOptions); - _mqttServer.StartAsync().GetAwaiter().GetResult(); + _mqttServer.StopAsync().GetAwaiter().GetResult(); + _mqttServer.Dispose(); + _mqttServer = null; + } - // Create publisher clients - _mqttPublisherClientsByPublisherName = new Dictionary(); - foreach (var pt in _topicsByPublisher) - { - var publisherName = pt.Key; - var mqttClient = clientFactory.CreateMqttClient(); - var publisherOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost") - .WithClientId(publisherName) - .WithKeepAlivePeriod(TimeSpan.FromSeconds(30)) - .Build(); - mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult(); - _mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient); - } + /// + /// 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; - // Create subscriber clients - _mqttSubscriberClients = new List(); - 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 => - { - // count messages and signal cancellation when expected message count is reached - lock (_lockMsgCount) - { - ++_messagesReceivedCount; - if (_messagesReceivedCount == _messagesExpectedCount) - { - _cancellationTokenSource.Cancel(); - } - } - return Task.CompletedTask; - }; - mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult(); - } + // Loop for a while and exchange messages + _messagesReceivedCount = 0; - List allTopics = new List(); - _publisherByTopic = new Dictionary(); - foreach (var t in _topicsByPublisher) - { - foreach (var topic in t.Value) - { - _publisherByTopic.Add(topic, t.Key); - allTopics.Add(topic); - } - } + _cancellationTokenSource = new CancellationTokenSource(); - // Subscribe to NumSubscribedTopics topics spread across all topics - _allSubscribedTopics = new List(); + // same payload for all messages + var payload = new byte[] { 1, 2, 3, 4 }; - var totalNumTopics = NumPublishers * NumTopicsPerPublisher; - int topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers); - if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics) - { - throw new System.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 - )); - } + var tasks = new List(); - var topicIndex = 0; - foreach (var mqttSubscriber in _mqttSubscriberClients) - { - for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep) - { - var topic = allTopics[topicIndex]; - _allSubscribedTopics.Add(topic); - var subOptions = new Client.MqttClientSubscribeOptionsBuilder().WithTopicFilter( - new Packets.MqttTopicFilter() { Topic = topic }) - .Build(); - mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult(); - } - } + // publish a message for each subscribed topic + foreach (var topic in _allSubscribedTopics) + { + var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build(); + // pick the correct publisher + var publisherName = _publisherByTopic[topic]; + var publisherClient = _mqttPublisherClientsByPublisherName[publisherName]; + _ = publisherClient.PublishAsync(message); + } - Task.Delay(1000).GetAwaiter().GetResult(); + // Wait one message per publish to be received by subscriber (in the subscriber's application message handler) + try + { + Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult(); + } + catch + { } - /// - /// Publish messages and wait for messages sent to subscribers - /// - [Benchmark] - public void DeliverMessages() + _cancellationTokenSource.Dispose(); + + if (_messagesReceivedCount < _messagesExpectedCount) { - // There should be one message received per publish for each subscribed topic - _messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers; + throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount)); + } + } - // Loop for a while and exchange messages - _messagesReceivedCount = 0; + [GlobalSetup] + public void Setup() + { + _lockMsgCount = new object(); - _cancellationTokenSource = new CancellationTokenSource(); + Dictionary> singleWildcardTopicsByPublisher; + Dictionary> multiWildcardTopicsByPublisher; - // same payload for all messages - var payload = new byte[] { 1, 2, 3, 4 }; + TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher); - var tasks = new List(); + 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); + } - // publish a message for each subscribed topic - foreach (var topic in _allSubscribedTopics) - { - var message = new MqttApplicationMessageBuilder() - .WithTopic(topic) - .WithPayload(payload) - .Build(); - // pick the correct publisher - var publisherName = _publisherByTopic[topic]; - var publisherClient = _mqttPublisherClientsByPublisherName[publisherName]; - _ = publisherClient.PublishAsync(message); - } + // Create server + var serverFactory = new MqttServerFactory(); + var clientFactory = new MqttClientFactory(); + var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); + _mqttServer = serverFactory.CreateMqttServer(serverOptions); + _mqttServer.StartAsync().GetAwaiter().GetResult(); - // Wait one message per publish to be received by subscriber (in the subscriber's application message handler) - try - { - Task.Delay(30000, _cancellationTokenSource.Token).GetAwaiter().GetResult(); - } - catch + // Create publisher clients + _mqttPublisherClientsByPublisherName = new Dictionary(); + foreach (var pt in _topicsByPublisher) + { + var publisherName = pt.Key; + var mqttClient = clientFactory.CreateMqttClient(); + var publisherOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId(publisherName).WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).Build(); + mqttClient.ConnectAsync(publisherOptions).GetAwaiter().GetResult(); + _mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient); + } + + // Create subscriber clients + _mqttSubscriberClients = new List(); + 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 => { + // count messages and signal cancellation when expected message count is reached + lock (_lockMsgCount) + { + ++_messagesReceivedCount; + if (_messagesReceivedCount == _messagesExpectedCount) + { + _cancellationTokenSource.Cancel(); + } + } - } + return Task.CompletedTask; + }; + mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult(); + } - _cancellationTokenSource.Dispose(); - if (_messagesReceivedCount < _messagesExpectedCount) + var allTopics = new List(); + _publisherByTopic = new Dictionary(); + foreach (var t in _topicsByPublisher) + { + foreach (var topic in t.Value) { - throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount)); + _publisherByTopic.Add(topic, t.Key); + allTopics.Add(topic); } } - [GlobalCleanup] - public void Cleanup() + // 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) { - foreach (var mp in _mqttPublisherClientsByPublisherName) - { - var mqttPublisherClient = mp.Value; - mqttPublisherClient.DisconnectAsync().GetAwaiter().GetResult(); - mqttPublisherClient.Dispose(); - } - _mqttPublisherClientsByPublisherName.Clear(); + 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)); + } - foreach (var mqttSubscriber in _mqttSubscriberClients) + var topicIndex = 0; + foreach (var mqttSubscriber in _mqttSubscriberClients) + { + for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep) { - mqttSubscriber.DisconnectAsync().GetAwaiter().GetResult(); - mqttSubscriber.Dispose(); + var topic = allTopics[topicIndex]; + _allSubscribedTopics.Add(topic); + var subOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(new MqttTopicFilter { Topic = topic }).Build(); + mqttSubscriber.SubscribeAsync(subOptions).GetAwaiter().GetResult(); } - _mqttSubscriberClients.Clear(); - - _mqttServer.StopAsync().GetAwaiter().GetResult(); - _mqttServer.Dispose(); - _mqttServer = null; } + + Task.Delay(1000).GetAwaiter().GetResult(); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs index 43c0745c4..b0c869244 100644 --- a/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs @@ -7,46 +7,43 @@ using MQTTnet.Client; using MQTTnet.Server; -namespace MQTTnet.Benchmarks +namespace MQTTnet.Benchmarks; + +[SimpleJob(RuntimeMoniker.Net60)] +[RPlotExporter] +[RankColumn] +[MemoryDiagnoser] +public class MessageProcessingBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [RPlotExporter, RankColumn] - [MemoryDiagnoser] - public class MessageProcessingBenchmark : BaseBenchmark - { - MqttServer _mqttServer; - IMqttClient _mqttClient; - MqttApplicationMessage _message; + MqttApplicationMessage _message; + IMqttClient _mqttClient; + MqttServer _mqttServer; - [GlobalSetup] - public void Setup() + [Benchmark] + public void Send_10000_Messages() + { + for (var i = 0; i < 10000; i++) { - var serverOptions = new MqttServerOptionsBuilder().Build(); + _mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); + } + } - var serverFactory = new MqttServerFactory(); - _mqttServer = serverFactory.CreateMqttServer(serverOptions); - var clientFactory = new MqttClientFactory(); - _mqttClient = clientFactory.CreateMqttClient(); + [GlobalSetup] + public void Setup() + { + var serverOptions = new MqttServerOptionsBuilder().Build(); - _mqttServer.StartAsync().GetAwaiter().GetResult(); + var serverFactory = new MqttServerFactory(); + _mqttServer = serverFactory.CreateMqttServer(serverOptions); + var clientFactory = new MqttClientFactory(); + _mqttClient = clientFactory.CreateMqttClient(); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); + _mqttServer.StartAsync().GetAwaiter().GetResult(); - _mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); - _message = new MqttApplicationMessageBuilder() - .WithTopic("A") - .Build(); - } + _mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); - [Benchmark] - public void Send_10000_Messages() - { - for (var i = 0; i < 10000; i++) - { - _mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); - } - } + _message = new MqttApplicationMessageBuilder().WithTopic("A").Build(); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 16406087c..1dc822c40 100644 --- a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -3,90 +3,86 @@ // See the LICENSE file in the project root for more information. using System; -using BenchmarkDotNet.Attributes; -using MQTTnet.Channel; -using MQTTnet.Implementations; -using MQTTnet.Server; +using System.Reflection; using System.Threading; using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; +using MQTTnet.Channel; using MQTTnet.Client; using MQTTnet.Diagnostics; -using MQTTnet.Server.Adapter; +using MQTTnet.Implementations; +using MQTTnet.Server; +using MQTTnet.Server.Internal.Adapter; + +namespace MQTTnet.Benchmarks; -namespace MQTTnet.Benchmarks +[SimpleJob(RuntimeMoniker.Net60)] +[MemoryDiagnoser] +public class MqttTcpChannelBenchmark : BaseBenchmark { - [SimpleJob(RuntimeMoniker.Net60)] - [MemoryDiagnoser] - public class MqttTcpChannelBenchmark : BaseBenchmark + IMqttChannel _clientChannel; + MqttServer _mqttServer; + IMqttChannel _serverChannel; + + [Benchmark] + public async Task Send_10000_Chunks() { - MqttServer _mqttServer; - IMqttChannel _serverChannel; - IMqttChannel _clientChannel; + var size = 5; + var iterations = 10000; + + await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); + } - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + var serverFactory = new MqttServerFactory(); + var tcpServer = new MqttTcpServerAdapter(); + tcpServer.ClientHandler += args => { - var serverFactory = new MqttServerFactory(); - var tcpServer = new MqttTcpServerAdapter(); - tcpServer.ClientHandler += args => - { - _serverChannel = - (IMqttChannel)args.GetType().GetField("_channel", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) - .GetValue(args); + _serverChannel = (IMqttChannel)args.GetType().GetField("_channel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(args); - return Task.CompletedTask; - }; + return Task.CompletedTask; + }; - var serverOptions = new MqttServerOptionsBuilder().Build(); - _mqttServer = serverFactory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger()); + var serverOptions = new MqttServerOptionsBuilder().Build(); + _mqttServer = serverFactory.CreateMqttServer(serverOptions, new[] { tcpServer }, new MqttNetEventLogger()); - _mqttServer.StartAsync().GetAwaiter().GetResult(); + _mqttServer.StartAsync().GetAwaiter().GetResult(); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); - var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions; - _clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions }); + var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions; + _clientChannel = new MqttTcpChannel(new MqttClientOptions { ChannelOptions = tcpOptions }); - _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); - } + _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult(); + } - [Benchmark] - public async Task Send_10000_Chunks() - { - var size = 5; - var iterations = 10000; + async Task ReadAsync(int iterations, int size) + { + await Task.Yield(); - await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size)); - } + var expected = iterations * size; + long read = 0; - async Task ReadAsync(int iterations, int size) + while (read < expected) { - await Task.Yield(); - - var expected = iterations * size; - long read = 0; - - while (read < expected) - { - var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); - read += readResult; - } + var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); + read += readResult; } + } - async Task WriteAsync(int iterations, int size) - { - await Task.Yield(); + async Task WriteAsync(int iterations, int size) + { + await Task.Yield(); - var buffer = new ArraySegment(new byte[size]); + var buffer = new ArraySegment(new byte[size]); - for (var i = 0; i < iterations; i++) - { - await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false); - } + for (var i = 0; i < iterations; i++) + { + await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/Program.cs b/Source/MQTTnet.Benchmarks/Program.cs index be231ff81..bff20e88d 100644 --- a/Source/MQTTnet.Benchmarks/Program.cs +++ b/Source/MQTTnet.Benchmarks/Program.cs @@ -71,7 +71,7 @@ static List CollectBenchmarks() { if (type != typeof(BaseBenchmark)) { - benchmarks.Add(type); + benchmarks.Add(type); } } } @@ -87,7 +87,7 @@ static void HandleArguments(string[] arguments) } // Allow for preselection to avoid developer frustration. - + if (int.TryParse(arguments[0], out var benchmarkIndex)) { _selectedBenchmarkIndex = benchmarkIndex; diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs index 4ac0d4f65..41ba0a821 100644 --- a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs @@ -31,7 +31,7 @@ public void GlobalSetup() .WithPayload(new byte[10 * 1024]) .Build(); - var packet = MqttPacketFactories.Publish.Create(mqttMessage); + var packet = MqttPublishPacketFactory.Create(mqttMessage); var buffer = mqttPacketFormatter.Encode(packet); stream = new MemoryStream(); diff --git a/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs b/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs index bb2b50a09..db4a8efaf 100644 --- a/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs @@ -2,66 +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.Collections.Generic; +using System.Linq; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; using MQTTnet.Client; using MQTTnet.Server; -using System.Collections.Generic; -using System.Linq; -namespace MQTTnet.Benchmarks -{ - [MemoryDiagnoser] - public class SubscribeBenchmark : BaseBenchmark - { - MqttServer _mqttServer; - IMqttClient _mqttClient; +namespace MQTTnet.Benchmarks; - const int NumPublishers = 1; - const int NumTopicsPerPublisher = 10000; +[MemoryDiagnoser] +public class SubscribeBenchmark : BaseBenchmark +{ + const int NumPublishers = 1; + const int NumTopicsPerPublisher = 10000; + IMqttClient _mqttClient; + MqttServer _mqttServer; - List _topics; + List _topics; - [GlobalSetup] - public void Setup() - { - TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); - _topics = topicsByPublisher.Values.First(); + [GlobalCleanup] + public void Cleanup() + { + _mqttClient.DisconnectAsync().GetAwaiter().GetResult(); + _mqttServer.StopAsync().GetAwaiter().GetResult(); + _mqttServer.Dispose(); + } - var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); + [GlobalSetup] + public void Setup() + { + TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); + _topics = topicsByPublisher.Values.First(); - var serverFactory = new MqttServerFactory(); - _mqttServer = serverFactory.CreateMqttServer(serverOptions); - var clientFactory = new MqttClientFactory(); - _mqttClient = clientFactory.CreateMqttClient(); + var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - _mqttServer.StartAsync().GetAwaiter().GetResult(); + var serverFactory = new MqttServerFactory(); + _mqttServer = serverFactory.CreateMqttServer(serverOptions); + var clientFactory = new MqttClientFactory(); + _mqttClient = clientFactory.CreateMqttClient(); - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost").Build(); + _mqttServer.StartAsync().GetAwaiter().GetResult(); - _mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); - } + var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(); - [GlobalCleanup] - public void Cleanup() - { - _mqttClient.DisconnectAsync().GetAwaiter().GetResult(); - _mqttServer.StopAsync().GetAwaiter().GetResult(); - _mqttServer.Dispose(); - } + _mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult(); + } - [Benchmark] - public void Subscribe_10000_Topics() + [Benchmark] + public void Subscribe_10000_Topics() + { + foreach (var topic in _topics) { - foreach (var topic in _topics) - { - var subscribeOptions = new MqttClientSubscribeOptionsBuilder() - .WithTopicFilter(topic, Protocol.MqttQualityOfServiceLevel.AtMostOnce) - .Build(); + var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build(); - _mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult(); - } + _mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult(); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs b/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs index 2914d2811..48b5edde0 100644 --- a/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs @@ -2,64 +2,57 @@ // 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.Linq; using BenchmarkDotNet.Attributes; using MQTTnet.Client; using MQTTnet.Server; -using System.Collections.Generic; -using System.Linq; -namespace MQTTnet.Benchmarks -{ - [MemoryDiagnoser] - public class UnsubscribeBenchmark : BaseBenchmark - { - MqttServer _mqttServer; - IMqttClient _mqttClient; +namespace MQTTnet.Benchmarks; - const int NumPublishers = 1; - const int NumTopicsPerPublisher = 10000; +[MemoryDiagnoser] +public class UnsubscribeBenchmark : BaseBenchmark +{ + const int NumPublishers = 1; + const int NumTopicsPerPublisher = 10000; + IMqttClient _mqttClient; + MqttServer _mqttServer; - List _topics; + List _topics; - [GlobalSetup] - public void Setup() - { - TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); - _topics = topicsByPublisher.Values.First(); + [GlobalSetup] + public void Setup() + { + TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); + _topics = topicsByPublisher.Values.First(); - var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); + var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); - var serverFactory = new MqttServerFactory(); - _mqttServer = serverFactory.CreateMqttServer(serverOptions); - var clientFactory = new MqttClientFactory(); - _mqttClient = clientFactory.CreateMqttClient(); + var serverFactory = new MqttServerFactory(); + _mqttServer = serverFactory.CreateMqttServer(serverOptions); + var clientFactory = new MqttClientFactory(); + _mqttClient = clientFactory.CreateMqttClient(); - _mqttServer.StartAsync().GetAwaiter().GetResult(); + _mqttServer.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(); - foreach (var topic in _topics) - { - var subscribeOptions = new MqttClientSubscribeOptionsBuilder() - .WithTopicFilter(topic) - .Build(); - _mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult(); - } + foreach (var topic in _topics) + { + var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build(); + _mqttClient.SubscribeAsync(subscribeOptions).GetAwaiter().GetResult(); } + } - [Benchmark] - public void Unsubscribe_10000_Topics() + [Benchmark] + public void Unsubscribe_10000_Topics() + { + foreach (var topic in _topics) { - foreach (var topic in _topics) - { - var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder() - .WithTopicFilter(topic) - .Build(); - _mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult(); - } + var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build(); + _mqttClient.UnsubscribeAsync(unsubscribeOptions).GetAwaiter().GetResult(); } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj index 219a17682..5b236d98f 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj +++ b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj @@ -41,11 +41,11 @@ - + - + diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs index aafa4d171..74c05ec57 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs @@ -5,26 +5,24 @@ using System; using MQTTnet.Client; -namespace MQTTnet.Extensions.ManagedClient -{ - public sealed class ManagedMqttClientOptions - { - public MqttClientOptions ClientOptions { get; set; } +namespace MQTTnet.Extensions.ManagedClient; - public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); +public sealed class ManagedMqttClientOptions +{ + public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); + public MqttClientOptions ClientOptions { get; set; } - public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1); + public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1); - public IManagedMqttClientStorage Storage { get; set; } + public int MaxPendingMessages { get; set; } = int.MaxValue; - public int MaxPendingMessages { get; set; } = int.MaxValue; + /// + /// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet. + /// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue. + /// + public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue; - public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage; + public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropNewMessage; - /// - /// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet. - /// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue. - /// - public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue; - } -} + public IManagedMqttClientStorage Storage { get; set; } +} \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj index a9be76699..fad9dee27 100644 --- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj +++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj @@ -41,11 +41,11 @@ - + - + diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj.DotSettings b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj.DotSettings index 1b491ba9f..18d916ce4 100644 --- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj.DotSettings +++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj.DotSettings @@ -1,3 +1,7 @@ - - True - True \ No newline at end of file + + True + True \ 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 f623d461d..5bd02a484 100644 --- a/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj +++ b/Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj @@ -8,9 +8,9 @@ The contributors of MQTTnet MQTTnet - This is an extension library which provides mqtt topic templating logic to support dispatch, - routing and similar functionality based on the well known moustache syntax, aka - AsyncAPI dynamic channel address. + This is an extension library which provides mqtt topic templating logic to support dispatch, + routing and similar functionality based on the well known moustache syntax, aka + AsyncAPI dynamic channel address. The contributors of MQTTnet MQTTnet.Extensions.TopicTemplate @@ -42,11 +42,11 @@ - + - + diff --git a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs index e9e61e9a6..763217fb1 100644 --- a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.cs +++ b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptions.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. -using System.Collections.Generic; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Disconnecting +namespace MQTTnet.Server { public sealed class MqttServerClientDisconnectOptions { diff --git a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs index 362585c65..7173ac64b 100644 --- a/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.cs +++ b/Source/MQTTnet.Server/Disconnecting/MqttServerClientDisconnectOptionsBuilder.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. -using System.Collections.Generic; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Disconnecting +namespace MQTTnet.Server { public sealed class MqttServerClientDisconnectOptionsBuilder { diff --git a/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs b/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs index 584c4d4ec..d75ed503a 100644 --- a/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs +++ b/Source/MQTTnet.Server/Events/PreparingSessionEventArgs.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using MQTTnet.Packets; +using MQTTnet.Server.Internal; namespace MQTTnet.Server { diff --git a/Source/MQTTnet.Server/Adapter/IMqttServerAdapter.cs b/Source/MQTTnet.Server/IMqttServerAdapter.cs similarity index 93% rename from Source/MQTTnet.Server/Adapter/IMqttServerAdapter.cs rename to Source/MQTTnet.Server/IMqttServerAdapter.cs index d2322e42a..3f5ac0ce7 100644 --- a/Source/MQTTnet.Server/Adapter/IMqttServerAdapter.cs +++ b/Source/MQTTnet.Server/IMqttServerAdapter.cs @@ -5,7 +5,7 @@ using MQTTnet.Adapter; using MQTTnet.Diagnostics; -namespace MQTTnet.Server.Adapter; +namespace MQTTnet.Server; public interface IMqttServerAdapter : IDisposable { diff --git a/Source/MQTTnet.Server/InjectedMqttApplicationMessage.cs b/Source/MQTTnet.Server/InjectedMqttApplicationMessage.cs index ed829b239..3a9add06a 100644 --- a/Source/MQTTnet.Server/InjectedMqttApplicationMessage.cs +++ b/Source/MQTTnet.Server/InjectedMqttApplicationMessage.cs @@ -2,27 +2,25 @@ // 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 InjectedMqttApplicationMessage { - public sealed class InjectedMqttApplicationMessage + public InjectedMqttApplicationMessage(MqttApplicationMessage applicationMessage) { - public InjectedMqttApplicationMessage(MqttApplicationMessage applicationMessage) - { - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - } + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + } - public MqttApplicationMessage ApplicationMessage { get; } + public MqttApplicationMessage ApplicationMessage { get; } - /// - /// Gets or sets the session items which should be used for all event handlers which are involved in message - /// processing. - /// If _null_ is specified the singleton session items from the server are used instead. - /// - public IDictionary CustomSessionItems { get; set; } + /// + /// Gets or sets the session items which should be used for all event handlers which are involved in message + /// processing. + /// If _null_ is specified the singleton session items from the server are used instead. + /// + public IDictionary CustomSessionItems { get; set; } - public string SenderClientId { get; set; } = string.Empty; - } + public string SenderClientId { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Adapter/MqttTcpServerAdapter.cs b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs similarity index 98% rename from Source/MQTTnet.Server/Adapter/MqttTcpServerAdapter.cs rename to Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs index 2751b95a4..921999b65 100644 --- a/Source/MQTTnet.Server/Adapter/MqttTcpServerAdapter.cs +++ b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerAdapter.cs @@ -8,7 +8,7 @@ using MQTTnet.Diagnostics; using MQTTnet.Internal; -namespace MQTTnet.Server.Adapter +namespace MQTTnet.Server.Internal.Adapter { public sealed class MqttTcpServerAdapter : IMqttServerAdapter { diff --git a/Source/MQTTnet.Server/Adapter/MqttTcpServerListener.cs b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs similarity index 99% rename from Source/MQTTnet.Server/Adapter/MqttTcpServerListener.cs rename to Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs index 7ad085c8a..b5cd9be1a 100644 --- a/Source/MQTTnet.Server/Adapter/MqttTcpServerListener.cs +++ b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs @@ -12,7 +12,7 @@ using MQTTnet.Implementations; using MQTTnet.Internal; -namespace MQTTnet.Server.Adapter +namespace MQTTnet.Server.Internal.Adapter { public sealed class MqttTcpServerListener : IDisposable { diff --git a/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs b/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs index 7c20e8d8d..298a6b21d 100644 --- a/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.cs +++ b/Source/MQTTnet.Server/Internal/CheckSubscriptionsResult.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. -using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class CheckSubscriptionsResult { diff --git a/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs b/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs index 12527f4d7..e7daeecc8 100644 --- a/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.cs +++ b/Source/MQTTnet.Server/Internal/DispatchApplicationMessageResult.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. -using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class DispatchApplicationMessageResult { diff --git a/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs b/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs index 742cb7ad6..0952efbb5 100644 --- a/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs +++ b/Source/MQTTnet.Server/Internal/EnqueueDataPacketResult.cs @@ -2,7 +2,7 @@ // 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.Internal { public enum EnqueueDataPacketResult { diff --git a/Source/MQTTnet.Server/Formatter/MqttConnAckPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttConnAckPacketFactory.cs similarity index 97% rename from Source/MQTTnet.Server/Formatter/MqttConnAckPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttConnAckPacketFactory.cs index e65798ba8..39e8a2ae1 100644 --- a/Source/MQTTnet.Server/Formatter/MqttConnAckPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttConnAckPacketFactory.cs @@ -6,7 +6,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Formatter; +namespace MQTTnet.Server.Internal.Formatter; public static class MqttConnAckPacketFactory { diff --git a/Source/MQTTnet.Server/Formatter/MqttDisconnectPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttDisconnectPacketFactory.cs similarity index 94% rename from Source/MQTTnet.Server/Formatter/MqttDisconnectPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttDisconnectPacketFactory.cs index a2e33e5f0..d7b4199ba 100644 --- a/Source/MQTTnet.Server/Formatter/MqttDisconnectPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttDisconnectPacketFactory.cs @@ -4,9 +4,8 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; -namespace MQTTnet.Server.Formatter; +namespace MQTTnet.Server.Internal.Formatter; public static class MqttDisconnectPacketFactory { diff --git a/Source/MQTTnet.Server/Formatter/MqttPubAckPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPubAckPacketFactory.cs similarity index 96% rename from Source/MQTTnet.Server/Formatter/MqttPubAckPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttPubAckPacketFactory.cs index 9e147d381..8e59eb7a9 100644 --- a/Source/MQTTnet.Server/Formatter/MqttPubAckPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPubAckPacketFactory.cs @@ -5,7 +5,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Formatter; +namespace MQTTnet.Server.Internal.Formatter; public static class MqttPubAckPacketFactory { diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttPubCompPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPubCompPacketFactory.cs new file mode 100644 index 000000000..958af7282 --- /dev/null +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPubCompPacketFactory.cs @@ -0,0 +1,26 @@ +// 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. + +using MQTTnet.Client; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Server.Internal.Formatter; + +public static class MqttPubCompPacketFactory +{ + public static MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode) + { + if (pubRelPacket == null) + { + throw new ArgumentNullException(nameof(pubRelPacket)); + } + + return new MqttPubCompPacket + { + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = (MqttPubCompReasonCode)(int)reasonCode + }; + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Formatter/MqttPubRecPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPubRecPacketFactory.cs similarity index 95% rename from Source/MQTTnet.Server/Formatter/MqttPubRecPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttPubRecPacketFactory.cs index cb37231d9..ad800a055 100644 --- a/Source/MQTTnet.Server/Formatter/MqttPubRecPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPubRecPacketFactory.cs @@ -5,7 +5,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server.Formatter; +namespace MQTTnet.Server.Internal.Formatter; public static class MqttPubRecPacketFactory { diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttPubRelPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPubRelPacketFactory.cs new file mode 100644 index 000000000..50c6f8852 --- /dev/null +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPubRelPacketFactory.cs @@ -0,0 +1,26 @@ +// 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. + +using MQTTnet.Client; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Server.Internal.Formatter; + +public static class MqttPubRelPacketFactory +{ + public static MqttPubRelPacket Create(MqttPubRecPacket pubRecPacket, MqttApplicationMessageReceivedReasonCode reasonCode) + { + if (pubRecPacket == null) + { + throw new ArgumentNullException(nameof(pubRecPacket)); + } + + return new MqttPubRelPacket + { + PacketIdentifier = pubRecPacket.PacketIdentifier, + ReasonCode = (MqttPubRelReasonCode)(int)reasonCode + }; + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs similarity index 59% rename from Source/MQTTnet.Server/Formatter/MqttPublishPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs index 33a18584c..6e747f4d0 100644 --- a/Source/MQTTnet.Server/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs @@ -2,12 +2,48 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Exceptions; using MQTTnet.Packets; -namespace MQTTnet.Server.Formatter; +namespace MQTTnet.Server.Internal.Formatter; public static class MqttPublishPacketFactory { + public static MqttPublishPacket Create(MqttConnectPacket connectPacket) + { + if (connectPacket == null) + { + throw new ArgumentNullException(nameof(connectPacket)); + } + + if (!connectPacket.WillFlag) + { + throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag)."); + } + + ArraySegment willMessageBuffer = default; + if (connectPacket.WillMessage?.Length > 0) + { + willMessageBuffer = new ArraySegment(connectPacket.WillMessage); + } + + var packet = new MqttPublishPacket + { + Topic = connectPacket.WillTopic, + PayloadSegment = willMessageBuffer, + QualityOfServiceLevel = connectPacket.WillQoS, + Retain = connectPacket.WillRetain, + ContentType = connectPacket.WillContentType, + CorrelationData = connectPacket.WillCorrelationData, + MessageExpiryInterval = connectPacket.WillMessageExpiryInterval, + PayloadFormatIndicator = connectPacket.WillPayloadFormatIndicator, + ResponseTopic = connectPacket.WillResponseTopic, + UserProperties = connectPacket.WillUserProperties + }; + + return packet; + } + public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) diff --git a/Source/MQTTnet.Server/Formatter/MqttSubAckPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttSubAckPacketFactory.cs similarity index 95% rename from Source/MQTTnet.Server/Formatter/MqttSubAckPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttSubAckPacketFactory.cs index 4f42e2664..38f4eeeaa 100644 --- a/Source/MQTTnet.Server/Formatter/MqttSubAckPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttSubAckPacketFactory.cs @@ -4,7 +4,7 @@ using MQTTnet.Packets; -namespace MQTTnet.Server.Formatter; +namespace MQTTnet.Server.Internal.Formatter; public static class MqttSubAckPacketFactory { diff --git a/Source/MQTTnet.Server/Formatter/MqttUnsubAckPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs similarity index 95% rename from Source/MQTTnet.Server/Formatter/MqttUnsubAckPacketFactory.cs rename to Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs index 46a517e2c..ad630882b 100644 --- a/Source/MQTTnet.Server/Formatter/MqttUnsubAckPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttUnsubAckPacketFactory.cs @@ -4,7 +4,7 @@ using MQTTnet.Packets; -namespace MQTTnet.Server.Formatter +namespace MQTTnet.Server.Internal.Formatter { public static class MqttUnsubAckPacketFactory { diff --git a/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs b/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs index bff6769af..af9538df6 100644 --- a/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs +++ b/Source/MQTTnet.Server/Internal/ISubscriptionChangedNotification.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public interface ISubscriptionChangedNotification { diff --git a/Source/MQTTnet.Server/Internal/MqttClient.cs b/Source/MQTTnet.Server/Internal/MqttClient.cs index 615703966..c2283c45c 100644 --- a/Source/MQTTnet.Server/Internal/MqttClient.cs +++ b/Source/MQTTnet.Server/Internal/MqttClient.cs @@ -10,571 +10,568 @@ using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; -using MQTTnet.Server.Formatter; -using MqttDisconnectPacketFactory = MQTTnet.Server.Formatter.MqttDisconnectPacketFactory; -using MqttPubAckPacketFactory = MQTTnet.Server.Formatter.MqttPubAckPacketFactory; -using MqttPublishPacketFactory = MQTTnet.Server.Formatter.MqttPublishPacketFactory; -using MqttPubRecPacketFactory = MQTTnet.Server.Formatter.MqttPubRecPacketFactory; - -namespace MQTTnet.Server +using MQTTnet.Server.Internal.Formatter; +using MqttDisconnectPacketFactory = MQTTnet.Server.Internal.Formatter.MqttDisconnectPacketFactory; +using MqttPubAckPacketFactory = MQTTnet.Server.Internal.Formatter.MqttPubAckPacketFactory; +using MqttPubCompPacketFactory = MQTTnet.Server.Internal.Formatter.MqttPubCompPacketFactory; +using MqttPublishPacketFactory = MQTTnet.Server.Internal.Formatter.MqttPublishPacketFactory; +using MqttPubRecPacketFactory = MQTTnet.Server.Internal.Formatter.MqttPubRecPacketFactory; +using MqttPubRelPacketFactory = MQTTnet.Server.Internal.Formatter.MqttPubRelPacketFactory; + +namespace MQTTnet.Server.Internal; + +public sealed class MqttClient : IDisposable { - public sealed class MqttClient : IDisposable + readonly MqttServerEventContainer _eventContainer; + readonly MqttNetSourceLogger _logger; + readonly MqttServerOptions _serverOptions; + readonly MqttClientSessionsManager _sessionsManager; + readonly Dictionary _topicAlias = new(); + + CancellationTokenSource _cancellationToken = new(); + bool _disconnectPacketSent; + + public MqttClient( + MqttConnectPacket connectPacket, + IMqttChannelAdapter channelAdapter, + MqttSession session, + MqttServerOptions serverOptions, + MqttServerEventContainer eventContainer, + MqttClientSessionsManager sessionsManager, + IMqttNetLogger logger) { - readonly MqttConnectPacket _connectPacket; - readonly MqttServerEventContainer _eventContainer; - readonly MqttNetSourceLogger _logger; - readonly MqttServerOptions _serverOptions; - readonly MqttClientSessionsManager _sessionsManager; - readonly Dictionary _topicAlias = new Dictionary(); - - CancellationTokenSource _cancellationToken = new CancellationTokenSource(); - bool _disconnectPacketSent; - - public MqttClient( - MqttConnectPacket connectPacket, - IMqttChannelAdapter channelAdapter, - MqttSession session, - MqttServerOptions serverOptions, - MqttServerEventContainer eventContainer, - MqttClientSessionsManager sessionsManager, - IMqttNetLogger logger) - { - _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); - _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); - _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); - _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); + _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); + _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); + _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); + ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); - ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); - Endpoint = channelAdapter.Endpoint; - Session = session ?? throw new ArgumentNullException(nameof(session)); + ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); + Endpoint = channelAdapter.Endpoint; + Session = session ?? throw new ArgumentNullException(nameof(session)); - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger.WithSource(nameof(MqttClient)); + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); } - public IMqttChannelAdapter ChannelAdapter { get; } + _logger = logger.WithSource(nameof(MqttClient)); + } - public MqttDisconnectPacket DisconnectPacket { get; set; } + public IMqttChannelAdapter ChannelAdapter { get; } - public string Endpoint { get; } + public MqttConnectPacket ConnectPacket { get; } - public string Id => _connectPacket.ClientId; + public MqttDisconnectPacket DisconnectPacket { get; private set; } - public bool IsRunning { get; private set; } + public string Endpoint { get; } - public bool IsTakenOver { get; set; } + public string Id => ConnectPacket.ClientId; - public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod; + public bool IsRunning { get; private set; } - public MqttSession Session { get; } + public bool IsTakenOver { get; set; } - public MqttClientStatistics Statistics { get; } = new MqttClientStatistics(); + public MqttSession Session { get; } - public void Dispose() - { - _cancellationToken?.Dispose(); - } + public MqttClientStatistics Statistics { get; } = new(); - public void ResetStatistics() - { - ChannelAdapter.ResetStatistics(); - Statistics.ResetStatistics(); - } + public void Dispose() + { + _cancellationToken?.Dispose(); + } - public async Task RunAsync() - { - _logger.Info("Client '{0}': Session started", Id); + public void ResetStatistics() + { + ChannelAdapter.ResetStatistics(); + Statistics.ResetStatistics(); + } - Session.LatestConnectPacket = _connectPacket; - Session.WillMessageSent = false; + public async Task RunAsync() + { + _logger.Info("Client '{0}': Session started", Id); - try - { - var cancellationToken = _cancellationToken.Token; - IsRunning = true; + Session.LatestConnectPacket = ConnectPacket; + Session.WillMessageSent = false; - _ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default) - .ConfigureAwait(false); + try + { + var cancellationToken = _cancellationToken.Token; + IsRunning = true; - await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false); - } - finally - { - IsRunning = false; + _ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false); - Session.DisconnectedTimestamp = DateTime.UtcNow; + await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false); + } + finally + { + IsRunning = false; - _cancellationToken?.TryCancel(); - _cancellationToken?.Dispose(); - _cancellationToken = null; - } + Session.DisconnectedTimestamp = DateTime.UtcNow; - var isCleanDisconnect = DisconnectPacket != null; + _cancellationToken?.TryCancel(); + _cancellationToken?.Dispose(); + _cancellationToken = null; + } - if (!IsTakenOver && !isCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent) - { - var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket); - var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket); + var isCleanDisconnect = DisconnectPacket != null; - _ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None); - Session.WillMessageSent = true; + if (!IsTakenOver && !isCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent) + { + var willPublishPacket = MqttPublishPacketFactory.Create(Session.LatestConnectPacket); + var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket); - _logger.Info("Client '{0}': Published will message", Id); - } + _ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None); + Session.WillMessageSent = true; - _logger.Info("Client '{0}': Connection stopped", Id); + _logger.Info("Client '{0}': Published will message", Id); } - public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) - { - packet = await InterceptPacketAsync(packet, cancellationToken).ConfigureAwait(false); - if (packet == null) - { - // The interceptor has decided that this packet will not used at all. - // This might break the protocol but the user wants that. - return; - } + _logger.Info("Client '{0}': Connection stopped", Id); + } - await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); - Statistics.HandleSentPacket(packet); + public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) + { + packet = await InterceptPacketAsync(packet, cancellationToken).ConfigureAwait(false); + if (packet == null) + { + // The interceptor has decided that this packet will not used at all. + // This might break the protocol but the user wants that. + return; } - public async Task StopAsync(MqttServerClientDisconnectOptions disconnectOptions) - { - IsRunning = false; + await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false); + Statistics.HandleSentPacket(packet); + } + + public async Task StopAsync(MqttServerClientDisconnectOptions disconnectOptions) + { + IsRunning = false; - if (!_disconnectPacketSent) + if (!_disconnectPacketSent) + { + // // Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+. + if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) { - // Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+. - if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) + // From RFC: The Client or Server MAY send a DISCONNECT packet before closing the Network Connection. + // This library does not sent a DISCONNECT packet for a normal disconnection. + // TODO: Maybe adding a configuration option is requested in the future. + if (disconnectOptions != null) { - // From RFC: The Client or Server MAY send a DISCONNECT packet before closing the Network Connection. - // This library does not sent a DISCONNECT packet for a normal disconnection. - // TODO: Maybe adding a configuration option is requested in the future. - if (disconnectOptions != null) + if (disconnectOptions.ReasonCode != MqttDisconnectReasonCode.NormalDisconnection || disconnectOptions.UserProperties?.Any() == true || + !string.IsNullOrEmpty(disconnectOptions.ReasonString) || !string.IsNullOrEmpty(disconnectOptions.ServerReference)) { - if (disconnectOptions.ReasonCode != MqttDisconnectReasonCode.NormalDisconnection || disconnectOptions.UserProperties?.Any() == true || - !string.IsNullOrEmpty(disconnectOptions.ReasonString) || !string.IsNullOrEmpty(disconnectOptions.ServerReference)) - { - // Is is very important to send the DISCONNECT packet here BEFORE cancelling the - // token because the entire connection is closed (disposed) as soon as the cancellation - // token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive - // at the client! - await TrySendDisconnectPacket(disconnectOptions).ConfigureAwait(false); - } + // It is very important to send the DISCONNECT packet here BEFORE cancelling the + // token because the entire connection is closed (disposed) as soon as the cancellation + // token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive + // at the client! + await TrySendDisconnectPacket(disconnectOptions).ConfigureAwait(false); } } } - - StopInternal(); } - Task ClientAcknowledgedPublishPacket(MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket) - { - if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers) - { - var eventArgs = new ClientAcknowledgedPublishPacketEventArgs(Id, Session.Items, publishPacket, acknowledgePacket); - return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger); - } - - return CompletedTask.Instance; - } + StopInternal(); + } - void HandleIncomingPingReqPacket() + Task ClientAcknowledgedPublishPacket(MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket) + { + if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers) { - // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1]. - Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance)); + var eventArgs = new ClientAcknowledgedPublishPacketEventArgs(Id, Session.Items, publishPacket, acknowledgePacket); + return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger); } - Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket) - { - var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier); + return CompletedTask.Instance; + } - if (acknowledgedPublishPacket != null) - { - return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket); - } + void HandleIncomingPingReqPacket() + { + // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1]. + Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance)); + } - return CompletedTask.Instance; - } + Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket) + { + var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier); - Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket) + if (acknowledgedPublishPacket != null) { - var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier); + return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket); + } - if (acknowledgedPublishPacket != null) - { - return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket); - } + return CompletedTask.Instance; + } - return CompletedTask.Instance; - } + Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket) + { + var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier); - async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + if (acknowledgedPublishPacket != null) { - HandleTopicAlias(publishPacket); + return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket); + } + + return CompletedTask.Instance; + } + + async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + { + HandleTopicAlias(publishPacket); - var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); + var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); - var dispatchApplicationMessageResult = - await _sessionsManager.DispatchApplicationMessage(Id, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false); + var dispatchApplicationMessageResult = await _sessionsManager.DispatchApplicationMessage(Id, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false); - if (dispatchApplicationMessageResult.CloseConnection) + if (dispatchApplicationMessageResult.CloseConnection) + { + await StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.UnspecifiedError }); + return; + } + + switch (publishPacket.QualityOfServiceLevel) + { + case MqttQualityOfServiceLevel.AtMostOnce: { - await StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.UnspecifiedError }); - return; + // Do nothing since QoS 0 has no ACK at all! + break; } - - switch (publishPacket.QualityOfServiceLevel) + case MqttQualityOfServiceLevel.AtLeastOnce: { - case MqttQualityOfServiceLevel.AtMostOnce: - { - // Do nothing since QoS 0 has no ACK at all! - break; - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - var pubAckPacket = MqttPubAckPacketFactory.Create(publishPacket, dispatchApplicationMessageResult); - Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket)); - break; - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - var pubRecPacket = MqttPubRecPacketFactory.Create(publishPacket, dispatchApplicationMessageResult); - Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket)); - break; - } - default: - { - throw new MqttCommunicationException("Received a not supported QoS level"); - } + var pubAckPacket = MqttPubAckPacketFactory.Create(publishPacket, dispatchApplicationMessageResult); + Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket)); + break; + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + var pubRecPacket = MqttPubRecPacketFactory.Create(publishPacket, dispatchApplicationMessageResult); + Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket)); + break; + } + default: + { + throw new MqttCommunicationException("Received a not supported QoS level"); } } + } - Task HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket) - { - // Do not fire the event _ClientAcknowledgedPublishPacket_ here because the QoS 2 process is only finished - // properly when the client has sent the PUBCOMP packet. - var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); - Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket)); + Task HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket) + { + // Do not fire the event _ClientAcknowledgedPublishPacket_ here because the QoS 2 process is only finished + // properly when the client has sent the PUBCOMP packet. + var pubRelPacket = MqttPubRelPacketFactory.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); + Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket)); - return CompletedTask.Instance; - } + return CompletedTask.Instance; + } - void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket) - { - var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); - Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket)); - } + void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket) + { + var pubCompPacket = MqttPubCompPacketFactory.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); + Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket)); + } - async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) - { - var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false); + async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + { + var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false); - var subAckPacket = MqttSubAckPacketFactory.Create(subscribePacket, subscribeResult); + var subAckPacket = MqttSubAckPacketFactory.Create(subscribePacket, subscribeResult); - Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket)); + Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket)); - if (subscribeResult.CloseConnection) - { - StopInternal(); - return; - } + if (subscribeResult.CloseConnection) + { + StopInternal(); + return; + } - if (subscribeResult.RetainedMessages != null) + if (subscribeResult.RetainedMessages != null) + { + foreach (var retainedMessageMatch in subscribeResult.RetainedMessages) { - foreach (var retainedMessageMatch in subscribeResult.RetainedMessages) - { - var publishPacket = MqttPublishPacketFactory.Create(retainedMessageMatch); - Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); - } + var publishPacket = MqttPublishPacketFactory.Create(retainedMessageMatch); + Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); } } + } - async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) - { - var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false); + async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + { + var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false); - var unsubAckPacket = MqttUnsubAckPacketFactory.Create(unsubscribePacket, unsubscribeResult); + var unsubAckPacket = MqttUnsubAckPacketFactory.Create(unsubscribePacket, unsubscribeResult); - Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket)); + Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket)); - if (unsubscribeResult.CloseConnection) - { - StopInternal(); - } + if (unsubscribeResult.CloseConnection) + { + StopInternal(); + } + } + + void HandleTopicAlias(MqttPublishPacket publishPacket) + { + if (publishPacket.TopicAlias == 0) + { + return; } - void HandleTopicAlias(MqttPublishPacket publishPacket) + lock (_topicAlias) { - if (publishPacket.TopicAlias == 0) + if (!string.IsNullOrEmpty(publishPacket.Topic)) { - return; + _topicAlias[publishPacket.TopicAlias] = publishPacket.Topic; } - - lock (_topicAlias) + else { - if (!string.IsNullOrEmpty(publishPacket.Topic)) + if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic)) { - _topicAlias[publishPacket.TopicAlias] = publishPacket.Topic; + publishPacket.Topic = topic; } else { - if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic)) - { - publishPacket.Topic = topic; - } - else - { - _logger.Warning("Client '{0}': Received invalid topic alias ({1})", Id, publishPacket.TopicAlias); - } + _logger.Warning("Client '{0}': Received invalid topic alias ({1})", Id, publishPacket.TopicAlias); } } } + } - async Task InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken) + async Task InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken) + { + if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers) { - if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers) - { - return packet; - } - - var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items); - await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false); + return packet; + } - if (!interceptingPacketEventArgs.ProcessPacket || packet == null) - { - return null; - } + var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items); + await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false); - return interceptingPacketEventArgs.Packet; + if (!interceptingPacketEventArgs.ProcessPacket || packet == null) + { + return null; } - async Task ReceivePackagesLoop(CancellationToken cancellationToken) + return interceptingPacketEventArgs.Packet; + } + + async Task ReceivePackagesLoop(CancellationToken cancellationToken) + { + MqttPacket currentPacket = null; + try { - MqttPacket currentPacket = null; - try + // We do not listen for the cancellation token here because the internal buffer might still + // contain data to be read even if the TCP connection was already dropped. So we rely on an + // own exception in the reading loop! + while (!cancellationToken.IsCancellationRequested) { - // We do not listen for the cancellation token here because the internal buffer might still - // contain data to be read even if the TCP connection was already dropped. So we rely on an - // own exception in the reading loop! - while (!cancellationToken.IsCancellationRequested) - { - await Task.Yield(); + await Task.Yield(); - currentPacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); - if (currentPacket == null) - { - return; - } + currentPacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); + if (currentPacket == null) + { + return; + } - // Check for cancellation again because receive packet might block some time. - if (cancellationToken.IsCancellationRequested) - { - return; - } + // Check for cancellation again because receive packet might block some time. + if (cancellationToken.IsCancellationRequested) + { + return; + } - // The TCP connection of this client may be still open but the client has already been taken over by - // a new TCP connection. So we must exit here to make sure to no longer process any message. - if (IsTakenOver || !IsRunning) - { - return; - } + // The TCP connection of this client may be still open but the client has already been taken over by + // a new TCP connection. So we must exit here to make sure to no longer process any message. + if (IsTakenOver || !IsRunning) + { + return; + } - var processPacket = true; + var processPacket = true; - if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers) - { - var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, currentPacket, Session.Items); - await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false); - currentPacket = interceptingPacketEventArgs.Packet; - processPacket = interceptingPacketEventArgs.ProcessPacket; - } + if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers) + { + var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, currentPacket, Session.Items); + await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false); + currentPacket = interceptingPacketEventArgs.Packet; + processPacket = interceptingPacketEventArgs.ProcessPacket; + } - if (!processPacket || currentPacket == null) - { - // Restart the receiving process to get the next packet ignoring the current one.. - continue; - } + if (!processPacket || currentPacket == null) + { + // Restart the receiving process to get the next packet ignoring the current one.. + continue; + } - Statistics.HandleReceivedPacket(currentPacket); + Statistics.HandleReceivedPacket(currentPacket); - if (currentPacket is MqttPublishPacket publishPacket) - { - await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false); - } - else if (currentPacket is MqttPubAckPacket pubAckPacket) - { - await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false); - } - else if (currentPacket is MqttPubCompPacket pubCompPacket) - { - await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false); - } - else if (currentPacket is MqttPubRecPacket pubRecPacket) - { - await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false); - } - else if (currentPacket is MqttPubRelPacket pubRelPacket) - { - HandleIncomingPubRelPacket(pubRelPacket); - } - else if (currentPacket is MqttSubscribePacket subscribePacket) - { - await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false); - } - else if (currentPacket is MqttUnsubscribePacket unsubscribePacket) - { - await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false); - } - else if (currentPacket is MqttPingReqPacket) - { - HandleIncomingPingReqPacket(); - } - else if (currentPacket is MqttPingRespPacket) - { - throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only."); - } - else if (currentPacket is MqttDisconnectPacket disconnectPacket) - { - DisconnectPacket = disconnectPacket; - return; - } - else - { - throw new MqttProtocolViolationException("Packet not allowed"); - } + if (currentPacket is MqttPublishPacket publishPacket) + { + await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false); } - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - if (exception is MqttCommunicationException) + else if (currentPacket is MqttPubAckPacket pubAckPacket) { - _logger.Warning(exception, "Client '{0}': Communication exception while receiving packets", Id); - return; + await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false); } - - var logLevel = MqttNetLogLevel.Error; - - if (!IsRunning) + else if (currentPacket is MqttPubCompPacket pubCompPacket) { - // There was an exception but the connection is already closed. So there is no chance to send a response to the client etc. - logLevel = MqttNetLogLevel.Warning; + await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false); } - - if (currentPacket == null) + else if (currentPacket is MqttPubRecPacket pubRecPacket) + { + await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false); + } + else if (currentPacket is MqttPubRelPacket pubRelPacket) + { + HandleIncomingPubRelPacket(pubRelPacket); + } + else if (currentPacket is MqttSubscribePacket subscribePacket) { - _logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id); + await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false); + } + else if (currentPacket is MqttUnsubscribePacket unsubscribePacket) + { + await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false); + } + else if (currentPacket is MqttPingReqPacket) + { + HandleIncomingPingReqPacket(); + } + else if (currentPacket is MqttPingRespPacket) + { + throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only."); + } + else if (currentPacket is MqttDisconnectPacket disconnectPacket) + { + DisconnectPacket = disconnectPacket; + return; } else { - _logger.Publish(logLevel, exception, "Client '{0}': Error while processing {1} packet", Id, currentPacket.GetRfcName()); + throw new MqttProtocolViolationException("Packet not allowed"); } } } - - async Task SendPacketsLoop(CancellationToken cancellationToken) + catch (OperationCanceledException) { - MqttPacketBusItem packetBusItem = null; - - try + } + catch (Exception exception) + { + if (exception is MqttCommunicationException) { - while (!cancellationToken.IsCancellationRequested && !IsTakenOver && IsRunning) - { - packetBusItem = await Session.DequeuePacketAsync(cancellationToken).ConfigureAwait(false); + _logger.Warning(exception, "Client '{0}': Communication exception while receiving packets", Id); + return; + } - // Also check the cancellation token here because the dequeue is blocking and may take some time. - if (cancellationToken.IsCancellationRequested) - { - return; - } + var logLevel = MqttNetLogLevel.Error; - if (IsTakenOver || !IsRunning) - { - return; - } + if (!IsRunning) + { + // There was an exception but the connection is already closed. So there is no chance to send a response to the client etc. + logLevel = MqttNetLogLevel.Warning; + } - try - { - await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false); - packetBusItem.Complete(); - } - catch (OperationCanceledException) - { - packetBusItem.Cancel(); - } - catch (Exception exception) - { - packetBusItem.Fail(exception); - } - finally - { - await Task.Yield(); - } - } + if (currentPacket == null) + { + _logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id); } - catch (OperationCanceledException) + else { + _logger.Publish(logLevel, exception, "Client '{0}': Error while processing {1} packet", Id, currentPacket.GetRfcName()); } - catch (Exception exception) + } + } + + async Task SendPacketsLoop(CancellationToken cancellationToken) + { + MqttPacketBusItem packetBusItem = null; + + try + { + while (!cancellationToken.IsCancellationRequested && !IsTakenOver && IsRunning) { - if (exception is MqttCommunicationTimedOutException) + packetBusItem = await Session.DequeuePacketAsync(cancellationToken).ConfigureAwait(false); + + // Also check the cancellation token here because the dequeue is blocking and may take some time. + if (cancellationToken.IsCancellationRequested) { - _logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to timeout", Id); + return; } - else if (exception is MqttCommunicationException) + + if (IsTakenOver || !IsRunning) { - _logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to communication exception", Id); + return; } - else + + try { - _logger.Error(exception, "Client '{0}': Sending PUBLISH packet failed", Id); + await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false); + packetBusItem.Complete(); } - - if (packetBusItem?.Packet is MqttPublishPacket publishPacket) + catch (OperationCanceledException) { - if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) - { - publishPacket.Dup = true; - Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); - } + packetBusItem.Cancel(); + } + catch (Exception exception) + { + packetBusItem.Fail(exception); + } + finally + { + await Task.Yield(); } - - StopInternal(); } } - - void StopInternal() + catch (OperationCanceledException) { - _cancellationToken?.TryCancel(); } - - async Task TrySendDisconnectPacket(MqttServerClientDisconnectOptions options) + catch (Exception exception) { - try + if (exception is MqttCommunicationTimedOutException) { - // This also indicates that it was tried at least! - _disconnectPacketSent = true; - - var disconnectPacket = MqttDisconnectPacketFactory.Create(options); + _logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to timeout", Id); + } + else if (exception is MqttCommunicationException) + { + _logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to communication exception", Id); + } + else + { + _logger.Error(exception, "Client '{0}': Sending PUBLISH packet failed", Id); + } - using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout)) + if (packetBusItem?.Packet is MqttPublishPacket publishPacket) + { + if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { - await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false); + publishPacket.Dup = true; + Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); } } - catch (Exception exception) + + StopInternal(); + } + } + + void StopInternal() + { + _cancellationToken?.TryCancel(); + } + + async Task TrySendDisconnectPacket(MqttServerClientDisconnectOptions options) + { + try + { + // This also indicates that it was tried at least! + _disconnectPacketSent = true; + + var disconnectPacket = MqttDisconnectPacketFactory.Create(options); + + using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout)) { - _logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1})", Id, options.ReasonCode); + await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false); } } + catch (Exception exception) + { + _logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1})", Id, options.ReasonCode); + } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs b/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs index b6acbb776..a62aacc55 100644 --- a/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttClientSessionsManager.cs @@ -11,744 +11,743 @@ using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; -using MQTTnet.Server.Formatter; -using MqttPublishPacketFactory = MQTTnet.Server.Formatter.MqttPublishPacketFactory; +using MQTTnet.Server.Internal.Formatter; +using MqttPublishPacketFactory = MQTTnet.Server.Internal.Formatter.MqttPublishPacketFactory; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal; + +public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification, IDisposable { - public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification, IDisposable - { - readonly Dictionary _clients = new Dictionary(4096); + readonly Dictionary _clients = new(4096); - readonly AsyncLock _createConnectionSyncRoot = new AsyncLock(); + readonly AsyncLock _createConnectionSyncRoot = new(); - readonly MqttServerEventContainer _eventContainer; - readonly MqttNetSourceLogger _logger; - readonly MqttServerOptions _options; + readonly MqttServerEventContainer _eventContainer; + readonly MqttNetSourceLogger _logger; + readonly MqttServerOptions _options; - readonly MqttRetainedMessagesManager _retainedMessagesManager; - readonly IMqttNetLogger _rootLogger; + readonly MqttRetainedMessagesManager _retainedMessagesManager; + readonly IMqttNetLogger _rootLogger; - readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim(); + readonly ReaderWriterLockSlim _sessionsManagementLock = new(); - // The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only. - // See the MqttSubscription object for a detailed explanation. - readonly MqttSessionsStorage _sessionsStorage = new MqttSessionsStorage(); - readonly HashSet _subscriberSessions = new HashSet(); + // The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only. + // See the MqttSubscription object for a detailed explanation. + readonly MqttSessionsStorage _sessionsStorage = new(); + readonly HashSet _subscriberSessions = new(); - public MqttClientSessionsManager( - MqttServerOptions options, - MqttRetainedMessagesManager retainedMessagesManager, - MqttServerEventContainer eventContainer, - IMqttNetLogger logger) + public MqttClientSessionsManager(MqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, MqttServerEventContainer eventContainer, IMqttNetLogger logger) + { + if (logger == null) { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } + throw new ArgumentNullException(nameof(logger)); + } - _logger = logger.WithSource(nameof(MqttClientSessionsManager)); - _rootLogger = logger; + _logger = logger.WithSource(nameof(MqttClientSessionsManager)); + _rootLogger = logger; - _options = options ?? throw new ArgumentNullException(nameof(options)); - _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); - } + _options = options ?? throw new ArgumentNullException(nameof(options)); + _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); + _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); + } - public async Task CloseAllConnections(MqttServerClientDisconnectOptions options) + public async Task CloseAllConnections(MqttServerClientDisconnectOptions options) + { + if (options == null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - List connections; - lock (_clients) - { - connections = _clients.Values.ToList(); - _clients.Clear(); - } + List connections; + lock (_clients) + { + connections = _clients.Values.ToList(); + _clients.Clear(); + } - foreach (var connection in connections) - { - await connection.StopAsync(options).ConfigureAwait(false); - } + foreach (var connection in connections) + { + await connection.StopAsync(options).ConfigureAwait(false); } + } + + public async Task DeleteSessionAsync(string clientId) + { + _logger.Verbose("Deleting session for client '{0}'.", clientId); - public async Task DeleteSessionAsync(string clientId) + MqttClient connection; + lock (_clients) { - _logger.Verbose("Deleting session for client '{0}'.", clientId); + _clients.TryGetValue(clientId, out connection); + } - MqttClient connection; - lock (_clients) + MqttSession session; + _sessionsManagementLock.EnterWriteLock(); + try + { + if (_sessionsStorage.TryRemoveSession(clientId, out session)) { - _clients.TryGetValue(clientId, out connection); + _subscriberSessions.Remove(session); } + } + finally + { + _sessionsManagementLock.ExitWriteLock(); + } - MqttSession session; - _sessionsManagementLock.EnterWriteLock(); - try - { - if (_sessionsStorage.TryRemoveSession(clientId, out session)) - { - _subscriberSessions.Remove(session); - } - } - finally + try + { + if (connection != null) { - _sessionsManagementLock.ExitWriteLock(); + await connection.StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.NormalDisconnection }).ConfigureAwait(false); } + } + catch (Exception exception) + { + _logger.Error(exception, "Error while deleting session '{0}'", clientId); + } - try - { - if (connection != null) - { - await connection.StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.NormalDisconnection }).ConfigureAwait(false); - } - } - catch (Exception exception) + try + { + if (_eventContainer.SessionDeletedEvent.HasHandlers && session != null) { - _logger.Error(exception, "Error while deleting session '{0}'", clientId); + var eventArgs = new SessionDeletedEventArgs(clientId, session.Items); + await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false); } + } + catch (Exception exception) + { + _logger.Error(exception, "Error while executing session deleted event for session '{0}'", clientId); + } - try - { - if (_eventContainer.SessionDeletedEvent.HasHandlers && session != null) - { - var eventArgs = new SessionDeletedEventArgs(clientId, session.Items); - await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false); - } - } - catch (Exception exception) + session?.Dispose(); + + _logger.Verbose("Session of client '{0}' deleted", clientId); + } + + public async Task DispatchApplicationMessage( + string senderId, + IDictionary senderSessionItems, + MqttApplicationMessage applicationMessage, + CancellationToken cancellationToken) + { + var processPublish = true; + var closeConnection = false; + string reasonString = null; + List userProperties = null; + var reasonCode = 0; // The reason code is later converted into several different but compatible enums! + + // Allow the user to intercept application message... + if (_eventContainer.InterceptingPublishEvent.HasHandlers) + { + var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems); + if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic)) { - _logger.Error(exception, "Error while executing session deleted event for session '{0}'", clientId); + // This can happen if a topic alias us used but the topic is + // unknown to the server. + interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid; + interceptingPublishEventArgs.ProcessPublish = false; } - session?.Dispose(); + await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false); - _logger.Verbose("Session of client '{0}' deleted", clientId); + applicationMessage = interceptingPublishEventArgs.ApplicationMessage; + closeConnection = interceptingPublishEventArgs.CloseConnection; + processPublish = interceptingPublishEventArgs.ProcessPublish; + reasonString = interceptingPublishEventArgs.Response.ReasonString; + userProperties = interceptingPublishEventArgs.Response.UserProperties; + reasonCode = (int)interceptingPublishEventArgs.Response.ReasonCode; } - public async Task DispatchApplicationMessage( - string senderId, - IDictionary senderSessionItems, - MqttApplicationMessage applicationMessage, - CancellationToken cancellationToken) + // Process the application message... + if (processPublish && applicationMessage != null) { - var processPublish = true; - var closeConnection = false; - string reasonString = null; - List userProperties = null; - var reasonCode = 0; // The reason code is later converted into several different but compatible enums! - - // Allow the user to intercept application message... - if (_eventContainer.InterceptingPublishEvent.HasHandlers) + var matchingSubscribersCount = 0; + try { - var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems); - if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic)) + if (applicationMessage.Retain) { - // This can happen if a topic alias us used but the topic is - // unknown to the server. - interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid; - interceptingPublishEventArgs.ProcessPublish = false; + await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false); } - await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false); + List subscriberSessions; + _sessionsManagementLock.EnterReadLock(); + try + { + subscriberSessions = _subscriberSessions.ToList(); + } + finally + { + _sessionsManagementLock.ExitReadLock(); + } - applicationMessage = interceptingPublishEventArgs.ApplicationMessage; - closeConnection = interceptingPublishEventArgs.CloseConnection; - processPublish = interceptingPublishEventArgs.ProcessPublish; - reasonString = interceptingPublishEventArgs.Response.ReasonString; - userProperties = interceptingPublishEventArgs.Response.UserProperties; - reasonCode = (int)interceptingPublishEventArgs.Response.ReasonCode; - } + // Calculate application message topic hash once for subscription checks + MqttTopicHash.Calculate(applicationMessage.Topic, out var topicHash, out _, out _); - // Process the application message... - if (processPublish && applicationMessage != null) - { - var matchingSubscribersCount = 0; - try + foreach (var session in subscriberSessions) { - if (applicationMessage.Retain) + if (!session.TryCheckSubscriptions(applicationMessage.Topic, topicHash, applicationMessage.QualityOfServiceLevel, senderId, out var checkSubscriptionsResult)) { - await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false); + // Checking the subscriptions has failed for the session. The session + // will be ignored. + continue; } - List subscriberSessions; - _sessionsManagementLock.EnterReadLock(); - try - { - subscriberSessions = _subscriberSessions.ToList(); - } - finally + if (!checkSubscriptionsResult.IsSubscribed) { - _sessionsManagementLock.ExitReadLock(); + continue; } - // Calculate application message topic hash once for subscription checks - MqttTopicHash.Calculate(applicationMessage.Topic, out var topicHash, out _, out _); - - foreach (var session in subscriberSessions) + if (_eventContainer.InterceptingClientEnqueueEvent.HasHandlers) { - if (!session.TryCheckSubscriptions( - applicationMessage.Topic, - topicHash, - applicationMessage.QualityOfServiceLevel, - senderId, - out var checkSubscriptionsResult)) - { - // Checking the subscriptions has failed for the session. The session - // will be ignored. - continue; - } + var eventArgs = new InterceptingClientApplicationMessageEnqueueEventArgs(senderId, session.Id, applicationMessage); + await _eventContainer.InterceptingClientEnqueueEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - if (!checkSubscriptionsResult.IsSubscribed) + if (!eventArgs.AcceptEnqueue) { + // Continue checking the other subscriptions continue; } + } - if (_eventContainer.InterceptingClientEnqueueEvent.HasHandlers) - { - var eventArgs = new InterceptingClientApplicationMessageEnqueueEventArgs(senderId, session.Id, applicationMessage); - await _eventContainer.InterceptingClientEnqueueEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - - if (!eventArgs.AcceptEnqueue) - { - // Continue checking the other subscriptions - continue; - } - } - - var publishPacketCopy = MqttPacketFactories.Publish.Create(applicationMessage); - publishPacketCopy.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; - publishPacketCopy.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers; - - if (publishPacketCopy.QualityOfServiceLevel > 0) - { - publishPacketCopy.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier(); - } - - if (checkSubscriptionsResult.RetainAsPublished) - { - // Transfer the original retain state from the publisher. This is a MQTTv5 feature. - publishPacketCopy.Retain = applicationMessage.Retain; - } - else - { - publishPacketCopy.Retain = false; - } + var publishPacketCopy = MqttPublishPacketFactory.Create(applicationMessage); + publishPacketCopy.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel; + publishPacketCopy.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers; - matchingSubscribersCount++; + if (publishPacketCopy.QualityOfServiceLevel > 0) + { + publishPacketCopy.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier(); + } - var result = session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketCopy)); + if (checkSubscriptionsResult.RetainAsPublished) + { + // Transfer the original retain state from the publisher. This is a MQTTv5 feature. + publishPacketCopy.Retain = applicationMessage.Retain; + } + else + { + publishPacketCopy.Retain = false; + } - if (_eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.HasHandlers) - { - var eventArgs = new ApplicationMessageEnqueuedEventArgs(senderId, session.Id, applicationMessage, result == EnqueueDataPacketResult.Dropped); - await _eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - } + matchingSubscribersCount++; - _logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'", session.Id, applicationMessage.Topic); - } + var result = session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketCopy)); - if (matchingSubscribersCount == 0) + if (_eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.HasHandlers) { - if (reasonCode == (int)MqttPubAckReasonCode.Success) - { - // Only change the value if it was success. Otherwise, we would hide an error or not authorized status. - reasonCode = (int)MqttPubAckReasonCode.NoMatchingSubscribers; - } - - await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false); + var eventArgs = new ApplicationMessageEnqueuedEventArgs(senderId, session.Id, applicationMessage, result == EnqueueDataPacketResult.Dropped); + await _eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } + + _logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'", session.Id, applicationMessage.Topic); } - catch (Exception exception) + + if (matchingSubscribersCount == 0) { - _logger.Error(exception, "Error while processing next queued application message"); + if (reasonCode == (int)MqttPubAckReasonCode.Success) + { + // Only change the value if it was success. Otherwise, we would hide an error or not authorized status. + reasonCode = (int)MqttPubAckReasonCode.NoMatchingSubscribers; + } + + await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false); } } - - return new DispatchApplicationMessageResult(reasonCode, closeConnection, reasonString, userProperties); + catch (Exception exception) + { + _logger.Error(exception, "Error while processing next queued application message"); + } } - public void Dispose() - { - _createConnectionSyncRoot.Dispose(); + return new DispatchApplicationMessageResult(reasonCode, closeConnection, reasonString, userProperties); + } - _sessionsManagementLock.EnterWriteLock(); - try - { - _sessionsStorage.Dispose(); - } - finally - { - _sessionsManagementLock.ExitWriteLock(); - } + public void Dispose() + { + _createConnectionSyncRoot.Dispose(); - _sessionsManagementLock?.Dispose(); + _sessionsManagementLock.EnterWriteLock(); + try + { + _sessionsStorage.Dispose(); } - - public MqttClient GetClient(string id) + finally { - lock (_clients) - { - if (!_clients.TryGetValue(id, out var client)) - { - throw new InvalidOperationException($"Client with ID '{id}' not found."); - } - - return client; - } + _sessionsManagementLock.ExitWriteLock(); } - public List GetClients() + _sessionsManagementLock?.Dispose(); + } + + public MqttClient GetClient(string id) + { + lock (_clients) { - lock (_clients) + if (!_clients.TryGetValue(id, out var client)) { - return _clients.Values.ToList(); + throw new InvalidOperationException($"Client with ID '{id}' not found."); } + + return client; } + } - public Task> GetClientsStatus() + public List GetClients() + { + lock (_clients) { - var result = new List(); + return _clients.Values.ToList(); + } + } + + public Task> GetClientsStatus() + { + var result = new List(); - lock (_clients) + lock (_clients) + { + foreach (var client in _clients.Values) { - foreach (var client in _clients.Values) + var clientStatus = new MqttClientStatus(client) { - var clientStatus = new MqttClientStatus(client) - { - Session = new MqttSessionStatus(client.Session) - }; + Session = new MqttSessionStatus(client.Session) + }; - result.Add(clientStatus); - } + result.Add(clientStatus); } - - return Task.FromResult((IList)result); } - public Task> GetSessionsStatus() - { - var result = new List(); + return Task.FromResult((IList)result); + } - _sessionsManagementLock.EnterReadLock(); - try - { - foreach (var session in _sessionsStorage.ReadAllSessions()) - { - var sessionStatus = new MqttSessionStatus(session); - result.Add(sessionStatus); - } - } - finally + public Task> GetSessionsStatus() + { + var result = new List(); + + _sessionsManagementLock.EnterReadLock(); + try + { + foreach (var session in _sessionsStorage.ReadAllSessions()) { - _sessionsManagementLock.ExitReadLock(); + var sessionStatus = new MqttSessionStatus(session); + result.Add(sessionStatus); } - - return Task.FromResult((IList)result); } - - public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + finally { - MqttClient client = null; + _sessionsManagementLock.ExitReadLock(); + } - try - { - var connectPacket = await ReceiveConnectPacket(channelAdapter, cancellationToken).ConfigureAwait(false); - if (connectPacket == null) - { - // Nothing was received in time etc. - return; - } + return Task.FromResult((IList)result); + } - var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false); - var connAckPacket = MqttConnAckPacketFactory.Create(validatingConnectionEventArgs); + public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + { + MqttClient client = null; - if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success) - { - // Send failure response here without preparing a connection and session! - await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); - return; - } + try + { + var connectPacket = await ReceiveConnectPacket(channelAdapter, cancellationToken).ConfigureAwait(false); + if (connectPacket == null) + { + // Nothing was received in time etc. + return; + } - // Pass connAckPacket so that IsSessionPresent flag can be set if the client session already exists. - client = await CreateClientConnection(connectPacket, connAckPacket, channelAdapter, validatingConnectionEventArgs).ConfigureAwait(false); + var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false); + var connAckPacket = MqttConnAckPacketFactory.Create(validatingConnectionEventArgs); - await client.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); + if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success) + { + // Send failure response here without preparing a connection and session! + await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); + return; + } - if (_eventContainer.ClientConnectedEvent.HasHandlers) - { - var eventArgs = new ClientConnectedEventArgs( - connectPacket, - channelAdapter.PacketFormatterAdapter.ProtocolVersion, - channelAdapter.Endpoint, - client.Session.Items); + // Pass connAckPacket so that IsSessionPresent flag can be set if the client session already exists. + client = await CreateClientConnection(connectPacket, connAckPacket, channelAdapter, validatingConnectionEventArgs).ConfigureAwait(false); - await _eventContainer.ClientConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false); - } + await client.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); - await client.RunAsync().ConfigureAwait(false); - } - catch (ObjectDisposedException) - { - } - catch (OperationCanceledException) - { - } - catch (Exception exception) + if (_eventContainer.ClientConnectedEvent.HasHandlers) { - _logger.Error(exception, exception.Message); + var eventArgs = new ClientConnectedEventArgs(connectPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion, channelAdapter.Endpoint, client.Session.Items); + + await _eventContainer.ClientConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false); } - finally + + await client.RunAsync().ConfigureAwait(false); + } + catch (ObjectDisposedException) + { + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + _logger.Error(exception, exception.Message); + } + finally + { + if (client != null) { - if (client != null) + if (client.Id != null) { - if (client.Id != null) + // in case it is a takeover _clientConnections already contains the new connection + if (!client.IsTakenOver) { - // in case it is a takeover _clientConnections already contains the new connection - if (!client.IsTakenOver) + lock (_clients) { - lock (_clients) - { - _clients.Remove(client.Id); - } - - if (!_options.EnablePersistentSessions || !client.Session.IsPersistent) - { - await DeleteSessionAsync(client.Id).ConfigureAwait(false); - } + _clients.Remove(client.Id); } - } - - var endpoint = client.Endpoint; - - if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers) - { - var disconnectType = client.DisconnectPacket != null ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean; - var eventArgs = new ClientDisconnectedEventArgs(client.Id, client.DisconnectPacket, disconnectType, endpoint, client.Session.Items); - await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + if (!_options.EnablePersistentSessions || !ShouldPersistSession(client)) + { + await DeleteSessionAsync(client.Id).ConfigureAwait(false); + } } } - using (var timeout = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) - { - await channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); - } - } - } + var endpoint = client.Endpoint; - public void OnSubscriptionsAdded(MqttSession clientSession, List topics) - { - _sessionsManagementLock.EnterWriteLock(); - try - { - if (!clientSession.HasSubscribedTopics) + if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers) { - // first subscribed topic - _subscriberSessions.Add(clientSession); - } + var disconnectType = client.DisconnectPacket != null ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean; + var eventArgs = new ClientDisconnectedEventArgs(client.Id, client.DisconnectPacket, disconnectType, endpoint, client.Session.Items); - foreach (var topic in topics) - { - clientSession.AddSubscribedTopic(topic); + await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } } - finally + + using (var timeout = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) { - _sessionsManagementLock.ExitWriteLock(); + await channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); } } + } - public void OnSubscriptionsRemoved(MqttSession clientSession, List subscriptionTopics) + public void OnSubscriptionsAdded(MqttSession clientSession, List topics) + { + _sessionsManagementLock.EnterWriteLock(); + try { - _sessionsManagementLock.EnterWriteLock(); - try + if (!clientSession.HasSubscribedTopics) { - foreach (var subscriptionTopic in subscriptionTopics) - { - clientSession.RemoveSubscribedTopic(subscriptionTopic); - } - - if (!clientSession.HasSubscribedTopics) - { - // last subscription removed - _subscriberSessions.Remove(clientSession); - } + // first subscribed topic + _subscriberSessions.Add(clientSession); } - finally + + foreach (var topic in topics) { - _sessionsManagementLock.ExitWriteLock(); + clientSession.AddSubscribedTopic(topic); } } - - public void Start() + finally { - if (!_options.EnablePersistentSessions) - { - _sessionsStorage.Clear(); - } + _sessionsManagementLock.ExitWriteLock(); } + } - public async Task SubscribeAsync(string clientId, ICollection topicFilters) + public void OnSubscriptionsRemoved(MqttSession clientSession, List subscriptionTopics) + { + _sessionsManagementLock.EnterWriteLock(); + try { - if (clientId == null) + foreach (var subscriptionTopic in subscriptionTopics) { - throw new ArgumentNullException(nameof(clientId)); + clientSession.RemoveSubscribedTopic(subscriptionTopic); } - if (topicFilters == null) + if (!clientSession.HasSubscribedTopics) { - throw new ArgumentNullException(nameof(topicFilters)); + // last subscription removed + _subscriberSessions.Remove(clientSession); } + } + finally + { + _sessionsManagementLock.ExitWriteLock(); + } + } - var fakeSubscribePacket = new MqttSubscribePacket(); - fakeSubscribePacket.TopicFilters.AddRange(topicFilters); - - var clientSession = GetClientSession(clientId); - - var subscribeResult = await clientSession.Subscribe(fakeSubscribePacket, CancellationToken.None).ConfigureAwait(false); + public void Start() + { + if (!_options.EnablePersistentSessions) + { + _sessionsStorage.Clear(); + } + } - if (subscribeResult.RetainedMessages != null) - { - foreach (var retainedMessageMatch in subscribeResult.RetainedMessages) - { - var publishPacket = MqttPublishPacketFactory.Create(retainedMessageMatch); - clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); - } - } + public async Task SubscribeAsync(string clientId, ICollection topicFilters) + { + if (clientId == null) + { + throw new ArgumentNullException(nameof(clientId)); } - public Task UnsubscribeAsync(string clientId, ICollection topicFilters) + if (topicFilters == null) { - if (clientId == null) - { - throw new ArgumentNullException(nameof(clientId)); - } + throw new ArgumentNullException(nameof(topicFilters)); + } - if (topicFilters == null) - { - throw new ArgumentNullException(nameof(topicFilters)); - } + var fakeSubscribePacket = new MqttSubscribePacket(); + fakeSubscribePacket.TopicFilters.AddRange(topicFilters); + + var clientSession = GetClientSession(clientId); - var fakeUnsubscribePacket = new MqttUnsubscribePacket(); - fakeUnsubscribePacket.TopicFilters.AddRange(topicFilters); + var subscribeResult = await clientSession.Subscribe(fakeSubscribePacket, CancellationToken.None).ConfigureAwait(false); - return GetClientSession(clientId).Unsubscribe(fakeUnsubscribePacket, CancellationToken.None); + if (subscribeResult.RetainedMessages != null) + { + foreach (var retainedMessageMatch in subscribeResult.RetainedMessages) + { + var publishPacket = MqttPublishPacketFactory.Create(retainedMessageMatch); + clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); + } } + } - MqttClient CreateClient(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session) + public Task UnsubscribeAsync(string clientId, ICollection topicFilters) + { + if (clientId == null) { - return new MqttClient(connectPacket, channelAdapter, session, _options, _eventContainer, this, _rootLogger); + throw new ArgumentNullException(nameof(clientId)); } - async Task CreateClientConnection( - MqttConnectPacket connectPacket, - MqttConnAckPacket connAckPacket, - IMqttChannelAdapter channelAdapter, - ValidatingConnectionEventArgs validatingConnectionEventArgs) + if (topicFilters == null) { - MqttClient client; + throw new ArgumentNullException(nameof(topicFilters)); + } - bool sessionShouldPersist; + var fakeUnsubscribePacket = new MqttUnsubscribePacket(); + fakeUnsubscribePacket.TopicFilters.AddRange(topicFilters); - if (validatingConnectionEventArgs.ProtocolVersion == MqttProtocolVersion.V500) - { - // MQTT 5.0 section 3.1.2.11.2 - // The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 [MQTT-3.1.2-23]. - // - // A Client that only wants to process messages while connected will set the Clean Start to 1 and set the Session Expiry Interval to 0. - // It will not receive Application Messages published before it connected and has to subscribe afresh to any topics that it is interested - // in each time it connects. + return GetClientSession(clientId).Unsubscribe(fakeUnsubscribePacket, CancellationToken.None); + } - // Persist if SessionExpiryInterval != 0, but may start with a clean session - sessionShouldPersist = validatingConnectionEventArgs.SessionExpiryInterval != 0; - } - else - { - // MQTT 3.1.1 section 3.1.2.4: persist only if 'not CleanSession' - // - // If CleanSession is set to 1, the Client and Server MUST discard any previous Session and start a new one. - // This Session lasts as long as the Network Connection. State data associated with this Session MUST NOT be - // reused in any subsequent Session [MQTT-3.1.2-6]. + MqttClient CreateClient(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session) + { + return new MqttClient(connectPacket, channelAdapter, session, _options, _eventContainer, this, _rootLogger); + } - sessionShouldPersist = !connectPacket.CleanSession; - } + async Task CreateClientConnection( + MqttConnectPacket connectPacket, + MqttConnAckPacket connAckPacket, + IMqttChannelAdapter channelAdapter, + ValidatingConnectionEventArgs validatingConnectionEventArgs) + { + MqttClient client; + + using (await _createConnectionSyncRoot.EnterAsync().ConfigureAwait(false)) + { + MqttSession oldSession; + MqttClient oldClient; - using (await _createConnectionSyncRoot.EnterAsync().ConfigureAwait(false)) + _sessionsManagementLock.EnterWriteLock(); + try { - MqttSession oldSession; - MqttClient oldClient; + MqttSession session; - _sessionsManagementLock.EnterWriteLock(); - try + // Create a new session (if required). + if (!_sessionsStorage.TryGetSession(connectPacket.ClientId, out oldSession)) { - MqttSession session; - - // Create a new session (if required). - if (!_sessionsStorage.TryGetSession(connectPacket.ClientId, out oldSession)) + session = CreateSession(connectPacket, validatingConnectionEventArgs); + } + else + { + if (connectPacket.CleanSession) { - session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist); + _logger.Verbose("Deleting existing session of client '{0}' due to clean start", connectPacket.ClientId); + _subscriberSessions.Remove(oldSession); + session = CreateSession(connectPacket, validatingConnectionEventArgs); } else { - if (connectPacket.CleanSession) - { - _logger.Verbose("Deleting existing session of client '{0}' due to clean start", connectPacket.ClientId); - _subscriberSessions.Remove(oldSession); - session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist); - } - else - { - _logger.Verbose("Reusing existing session of client '{0}'", connectPacket.ClientId); - session = oldSession; - oldSession = null; + _logger.Verbose("Reusing existing session of client '{0}'", connectPacket.ClientId); + session = oldSession; + oldSession = null; - // Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval - session.IsPersistent = sessionShouldPersist; - session.DisconnectedTimestamp = null; - session.Recover(); + session.DisconnectedTimestamp = null; + session.Recover(); - connAckPacket.IsSessionPresent = true; - } + connAckPacket.IsSessionPresent = true; } + } - _sessionsStorage.UpdateSession(connectPacket.ClientId, session); + _sessionsStorage.UpdateSession(connectPacket.ClientId, session); - // Create a new client (always required). - lock (_clients) + // Create a new client (always required). + lock (_clients) + { + _clients.TryGetValue(connectPacket.ClientId, out oldClient); + if (oldClient != null) { - _clients.TryGetValue(connectPacket.ClientId, out oldClient); - if (oldClient != null) - { - // This will stop the current client from sending and receiving but remains the connection active - // for a later DISCONNECT packet. - oldClient.IsTakenOver = true; - } - - client = CreateClient(connectPacket, channelAdapter, session); - _clients[connectPacket.ClientId] = client; + // This will stop the current client from sending and receiving but remains the connection active + // for a later DISCONNECT packet. + oldClient.IsTakenOver = true; } - } - finally - { - _sessionsManagementLock.ExitWriteLock(); - } - if (!connAckPacket.IsSessionPresent) - { - // TODO: This event is not yet final. It can already be used but restoring sessions from storage will be added later! - var preparingSessionEventArgs = new PreparingSessionEventArgs(); - await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false); + client = CreateClient(connectPacket, channelAdapter, session); + _clients[connectPacket.ClientId] = client; } + } + finally + { + _sessionsManagementLock.ExitWriteLock(); + } - if (oldClient != null) - { - // TODO: Consider event here for session takeover to allow manipulation of user properties etc. - await oldClient.StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.SessionTakenOver }).ConfigureAwait(false); + if (!connAckPacket.IsSessionPresent) + { + // TODO: This event is not yet final. It can already be used but restoring sessions from storage will be added later! + var preparingSessionEventArgs = new PreparingSessionEventArgs(); + await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false); + } - if (_eventContainer.ClientDisconnectedEvent.HasHandlers) - { - var eventArgs = new ClientDisconnectedEventArgs(oldClient.Id, null, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items); + if (oldClient != null) + { + // TODO: Consider event here for session takeover to allow manipulation of user properties etc. + await oldClient.StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.SessionTakenOver }).ConfigureAwait(false); - await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false); - } - } + if (_eventContainer.ClientDisconnectedEvent.HasHandlers) + { + var eventArgs = new ClientDisconnectedEventArgs(oldClient.Id, null, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items); - oldSession?.Dispose(); + await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false); + } } - return client; + oldSession?.Dispose(); } - MqttSession CreateSession(MqttConnectPacket connectPacket, ValidatingConnectionEventArgs validatingConnectionEventArgs, bool isPersistent) - { - _logger.Verbose("Created new session for client '{0}'", connectPacket.ClientId); + return client; + } - return new MqttSession(connectPacket, validatingConnectionEventArgs.SessionItems, _options, _eventContainer, _retainedMessagesManager, this) - { - IsPersistent = isPersistent - }; + MqttSession CreateSession(MqttConnectPacket connectPacket, ValidatingConnectionEventArgs validatingConnectionEventArgs) + { + _logger.Verbose("Created new session for client '{0}'", connectPacket.ClientId); + + return new MqttSession(connectPacket, validatingConnectionEventArgs.SessionItems, _options, _eventContainer, _retainedMessagesManager, this); + } + + async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId) + { + if (!_eventContainer.ApplicationMessageNotConsumedEvent.HasHandlers) + { + return; } - async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId) + var eventArgs = new ApplicationMessageNotConsumedEventArgs(applicationMessage, senderId); + await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + } + + MqttSession GetClientSession(string clientId) + { + _sessionsManagementLock.EnterReadLock(); + try { - if (!_eventContainer.ApplicationMessageNotConsumedEvent.HasHandlers) + if (!_sessionsStorage.TryGetSession(clientId, out var session)) { - return; + throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } - var eventArgs = new ApplicationMessageNotConsumedEventArgs(applicationMessage, senderId); - await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + return session; } + finally + { + _sessionsManagementLock.ExitReadLock(); + } + } - MqttSession GetClientSession(string clientId) + async Task ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + { + try { - _sessionsManagementLock.EnterReadLock(); - try + using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) + using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken)) { - if (!_sessionsStorage.TryGetSession(clientId, out var session)) + var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false); + if (firstPacket is MqttConnectPacket connectPacket) { - throw new InvalidOperationException($"Client session '{clientId}' is unknown."); + return connectPacket; } - - return session; - } - finally - { - _sessionsManagementLock.ExitReadLock(); } } + catch (OperationCanceledException) + { + _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); + } + catch (MqttCommunicationTimedOutException) + { + _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); + } - async Task ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + _logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint); + return null; + } + + static bool ShouldPersistSession(MqttClient client) + { + switch (client.ChannelAdapter.PacketFormatterAdapter.ProtocolVersion) { - try + case MqttProtocolVersion.V500: { - using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) - using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken)) + // MQTT 5.0 section 3.1.2.11.2 + // The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 [MQTT-3.1.2-23]. + // + // A Client that only wants to process messages while connected will set the Clean Start to 1 and set the Session Expiry Interval to 0. + // It will not receive Application Messages published before it connected and has to subscribe afresh to any topics that it is interested + // in each time it connects. + + var effectiveSessionExpiryInterval = client.DisconnectPacket?.SessionExpiryInterval ?? 0U; + if (effectiveSessionExpiryInterval == 0U) { - var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false); - if (firstPacket is MqttConnectPacket connectPacket) - { - return connectPacket; - } + // From RFC: If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. + effectiveSessionExpiryInterval = client.ConnectPacket.SessionExpiryInterval; } + + return effectiveSessionExpiryInterval != 0U; } - catch (OperationCanceledException) + + case MqttProtocolVersion.V311: { - _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); + // MQTT 3.1.1 section 3.1.2.4: persist only if 'not CleanSession' + // + // If CleanSession is set to 1, the Client and Server MUST discard any previous Session and start a new one. + // This Session lasts as long as the Network Connection. State data associated with this Session MUST NOT be + // reused in any subsequent Session [MQTT-3.1.2-6]. + + return !client.ConnectPacket.CleanSession; } - catch (MqttCommunicationTimedOutException) + + case MqttProtocolVersion.V310: { - _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); + return true; } - _logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint); - return null; + default: + throw new NotSupportedException(); } + } - async Task ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) - { - // TODO: Load session items from persisted sessions in the future. - var sessionItems = new ConcurrentDictionary(); - var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter, sessionItems); - await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - - // Check the client ID and set a random one if supported. - if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) - { - connectPacket.ClientId = eventArgs.AssignedClientIdentifier; - } + async Task ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) + { + // TODO: Load session items from persisted sessions in the future. + var sessionItems = new ConcurrentDictionary(); + var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter, sessionItems); + await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - if (string.IsNullOrEmpty(connectPacket.ClientId)) - { - eventArgs.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; - } + // Check the client ID and set a random one if supported. + if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) + { + connectPacket.ClientId = eventArgs.AssignedClientIdentifier; + } - return eventArgs; + if (string.IsNullOrEmpty(connectPacket.ClientId)) + { + eventArgs.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; } + + return eventArgs; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs b/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs index 0d6867bdc..baa2a3bd7 100644 --- a/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs +++ b/Source/MQTTnet.Server/Internal/MqttClientStatistics.cs @@ -2,11 +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.Threading; using MQTTnet.Packets; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttClientStatistics { diff --git a/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs b/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs index 8cacf5605..96c0eafe0 100644 --- a/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttClientSubscriptionsManager.cs @@ -2,16 +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. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttClientSubscriptionsManager : IDisposable { diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index 9035a7765..fb411eca7 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -2,14 +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. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using MQTTnet.Diagnostics; using MQTTnet.Internal; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttRetainedMessagesManager { diff --git a/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs b/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs index 1ffbf88db..9392be6d7 100644 --- a/Source/MQTTnet.Server/Internal/MqttServerEventContainer.cs +++ b/Source/MQTTnet.Server/Internal/MqttServerEventContainer.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. -using System; using MQTTnet.Internal; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttServerEventContainer { diff --git a/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs b/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs index 91b7ffe40..3d3f009cb 100644 --- a/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs +++ b/Source/MQTTnet.Server/Internal/MqttServerKeepAliveMonitor.cs @@ -2,15 +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; -using System.Threading; -using System.Threading.Tasks; using MQTTnet.Diagnostics; using MQTTnet.Internal; using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttServerKeepAliveMonitor { @@ -97,7 +93,7 @@ void TryProcessClient(MqttClient connection, DateTime now) return; } - if (connection.KeepAlivePeriod == 0) + if (connection.ConnectPacket.KeepAlivePeriod == 0) { // The keep alive feature is not used by the current connection. return; @@ -106,7 +102,7 @@ void TryProcessClient(MqttClient connection, DateTime now) // 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.KeepAlivePeriod * 1.5D; + var maxSecondsWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D; var secondsWithoutPackage = (now - connection.Statistics.LastPacketSentTimestamp).TotalSeconds; if (secondsWithoutPackage < maxSecondsWithoutPacket) diff --git a/Source/MQTTnet.Server/Internal/MqttSession.cs b/Source/MQTTnet.Server/Internal/MqttSession.cs index 40f1b4f00..8f68bd782 100644 --- a/Source/MQTTnet.Server/Internal/MqttSession.cs +++ b/Source/MQTTnet.Server/Internal/MqttSession.cs @@ -2,241 +2,229 @@ // 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.Linq; -using System.Threading; -using System.Threading.Tasks; using MQTTnet.Client; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal; + +public sealed class MqttSession : IDisposable { - public sealed class MqttSession : IDisposable + readonly MqttClientSessionsManager _clientSessionsManager; + readonly MqttConnectPacket _connectPacket; + readonly MqttServerEventContainer _eventContainer; + readonly MqttPacketBus _packetBus = new(); + readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new(); + readonly MqttServerOptions _serverOptions; + readonly MqttClientSubscriptionsManager _subscriptionsManager; + + // Do not use a dictionary in order to keep the ordering of the messages. + readonly List _unacknowledgedPublishPackets = new(); + + // Bookkeeping to know if this is a subscribing client; lazy initialize later. + HashSet _subscribedTopics; + + public MqttSession( + MqttConnectPacket connectPacket, + IDictionary items, + MqttServerOptions serverOptions, + MqttServerEventContainer eventContainer, + MqttRetainedMessagesManager retainedMessagesManager, + MqttClientSessionsManager clientSessionsManager) { - readonly MqttClientSessionsManager _clientSessionsManager; - readonly MqttServerEventContainer _eventContainer; - readonly MqttPacketBus _packetBus = new MqttPacketBus(); - readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); - - readonly MqttConnectPacket _connectPacket; - readonly MqttServerOptions _serverOptions; - readonly MqttClientSubscriptionsManager _subscriptionsManager; - - // Do not use a dictionary in order to keep the ordering of the messages. - readonly List _unacknowledgedPublishPackets = new List(); - - // Bookkeeping to know if this is a subscribing client; lazy initialize later. - HashSet _subscribedTopics; - - public MqttSession( - MqttConnectPacket connectPacket, - IDictionary items, - MqttServerOptions serverOptions, - MqttServerEventContainer eventContainer, - MqttRetainedMessagesManager retainedMessagesManager, - MqttClientSessionsManager clientSessionsManager) - { - Items = items ?? throw new ArgumentNullException(nameof(items)); + Items = items ?? throw new ArgumentNullException(nameof(items)); - _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); - _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); - _clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager)); - _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); + _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); + _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); + _clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager)); + _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); - _subscriptionsManager = new MqttClientSubscriptionsManager(this, eventContainer, retainedMessagesManager, clientSessionsManager); - } - - public DateTime CreatedTimestamp { get; } = DateTime.UtcNow; - - public DateTime? DisconnectedTimestamp { get; set; } + _subscriptionsManager = new MqttClientSubscriptionsManager(this, eventContainer, retainedMessagesManager, clientSessionsManager); + } - public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0; + public DateTime CreatedTimestamp { get; } = DateTime.UtcNow; - public uint ExpiryInterval => _connectPacket.SessionExpiryInterval; - - public string Id => _connectPacket.ClientId; + public DateTime? DisconnectedTimestamp { get; set; } - /// - /// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5) - /// - public bool IsPersistent { get; set; } + public uint ExpiryInterval => _connectPacket.SessionExpiryInterval; - public IDictionary Items { get; } + public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0; - public MqttConnectPacket LatestConnectPacket { get; set; } + public string Id => _connectPacket.ClientId; - public MqttPacketIdentifierProvider PacketIdentifierProvider { get; } = new MqttPacketIdentifierProvider(); + public IDictionary Items { get; } - public long PendingDataPacketsCount => _packetBus.PartitionItemsCount(MqttPacketBusPartition.Data); + public MqttConnectPacket LatestConnectPacket { get; set; } - public bool WillMessageSent { get; set; } + public MqttPacketIdentifierProvider PacketIdentifierProvider { get; } = new(); - public MqttPublishPacket AcknowledgePublishPacket(ushort packetIdentifier) - { - MqttPublishPacket publishPacket; + public long PendingDataPacketsCount => _packetBus.PartitionItemsCount(MqttPacketBusPartition.Data); - lock (_unacknowledgedPublishPackets) - { - publishPacket = _unacknowledgedPublishPackets.FirstOrDefault(p => p.PacketIdentifier.Equals(packetIdentifier)); - _unacknowledgedPublishPackets.Remove(publishPacket); - } + public bool WillMessageSent { get; set; } - return publishPacket; - } + public MqttPublishPacket AcknowledgePublishPacket(ushort packetIdentifier) + { + MqttPublishPacket publishPacket; - public void AddSubscribedTopic(string topic) + lock (_unacknowledgedPublishPackets) { - if (_subscribedTopics == null) - { - _subscribedTopics = new HashSet(); - } - - _subscribedTopics.Add(topic); + publishPacket = _unacknowledgedPublishPackets.FirstOrDefault(p => p.PacketIdentifier.Equals(packetIdentifier)); + _unacknowledgedPublishPackets.Remove(publishPacket); } - public Task DeleteAsync() - { - return _clientSessionsManager.DeleteSessionAsync(Id); - } + return publishPacket; + } - public Task DequeuePacketAsync(CancellationToken cancellationToken) + public void AddSubscribedTopic(string topic) + { + if (_subscribedTopics == null) { - return _packetBus.DequeueItemAsync(cancellationToken); + _subscribedTopics = new HashSet(); } - public void Dispose() - { - _packetBus.Dispose(); - _subscriptionsManager.Dispose(); - } + _subscribedTopics.Add(topic); + } - public void EnqueueControlPacket(MqttPacketBusItem packetBusItem) - { - _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Control); - } + public Task DeleteAsync() + { + return _clientSessionsManager.DeleteSessionAsync(Id); + } - public EnqueueDataPacketResult EnqueueDataPacket(MqttPacketBusItem packetBusItem) - { - if (_packetBus.ItemsCount(MqttPacketBusPartition.Data) >= _serverOptions.MaxPendingMessagesPerClient) - { - if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) - { - return EnqueueDataPacketResult.Dropped; - } + public Task DequeuePacketAsync(CancellationToken cancellationToken) + { + return _packetBus.DequeueItemAsync(cancellationToken); + } - if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) - { - // Only drop from the data partition. Dropping from control partition might break the connection - // because the client does not receive PINGREQ packets etc. any longer. - var firstItem = _packetBus.DropFirstItem(MqttPacketBusPartition.Data); - if (firstItem != null && _eventContainer.QueuedApplicationMessageOverwrittenEvent.HasHandlers) - { - var eventArgs = new QueueMessageOverwrittenEventArgs(Id, firstItem.Packet); - _eventContainer.QueuedApplicationMessageOverwrittenEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - } - } - } + public void Dispose() + { + _packetBus.Dispose(); + _subscriptionsManager.Dispose(); + } - var publishPacket = (MqttPublishPacket)packetBusItem.Packet; + public void EnqueueControlPacket(MqttPacketBusItem packetBusItem) + { + _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Control); + } - if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) + public EnqueueDataPacketResult EnqueueDataPacket(MqttPacketBusItem packetBusItem) + { + if (_packetBus.ItemsCount(MqttPacketBusPartition.Data) >= _serverOptions.MaxPendingMessagesPerClient) + { + if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + return EnqueueDataPacketResult.Dropped; + } - lock (_unacknowledgedPublishPackets) + if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) + { + // Only drop from the data partition. Dropping from control partition might break the connection + // because the client does not receive PINGREQ packets etc. any longer. + var firstItem = _packetBus.DropFirstItem(MqttPacketBusPartition.Data); + if (firstItem != null && _eventContainer.QueuedApplicationMessageOverwrittenEvent.HasHandlers) { - _unacknowledgedPublishPackets.Add(publishPacket); + var eventArgs = new QueueMessageOverwrittenEventArgs(Id, firstItem.Packet); + _eventContainer.QueuedApplicationMessageOverwrittenEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } } - - _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Data); - return EnqueueDataPacketResult.Enqueued; } - public void EnqueueHealthPacket(MqttPacketBusItem packetBusItem) - { - _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health); - } + var publishPacket = (MqttPublishPacket)packetBusItem.Packet; - public MqttPublishPacket PeekAcknowledgePublishPacket(ushort packetIdentifier) + if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) { - // This will only return the matching PUBLISH packet but does not remove it. - // This is required for QoS 2. + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + lock (_unacknowledgedPublishPackets) { - return _unacknowledgedPublishPackets.FirstOrDefault(p => p.PacketIdentifier.Equals(packetIdentifier)); + _unacknowledgedPublishPackets.Add(publishPacket); } } - public void Recover() - { - // TODO: Keep the bus and only insert pending items again. - // TODO: Check if packet identifier must be restarted or not. - // TODO: Recover package identifier. - - /* - The Session state in the Client consists of: - · QoS 1 and QoS 2 messages which have been sent to the Server, but have not been completely acknowledged. - · QoS 2 messages which have been received from the Server, but have not been completely acknowledged. - - The Session state in the Server consists of: - · The existence of a Session, even if the rest of the Session state is empty. - · The Client’s subscriptions. - · 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. - · QoS 2 messages which have been received from the Client, but have not been completely acknowledged. - · Optionally, QoS 0 messages pending transmission to the Client. - */ - - // Create a copy of all currently unacknowledged publish packets and clear the storage. - // We must re-enqueue them in order to trigger other code. - List unacknowledgedPublishPackets; - lock (_unacknowledgedPublishPackets) - { - unacknowledgedPublishPackets = _unacknowledgedPublishPackets.ToList(); - _unacknowledgedPublishPackets.Clear(); - } + _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Data); + return EnqueueDataPacketResult.Enqueued; + } - _packetBus.Clear(); + public void EnqueueHealthPacket(MqttPacketBusItem packetBusItem) + { + _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health); + } - foreach (var publishPacket in unacknowledgedPublishPackets) - { - EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); - } + public MqttPublishPacket PeekAcknowledgePublishPacket(ushort packetIdentifier) + { + // This will only return the matching PUBLISH packet but does not remove it. + // This is required for QoS 2. + lock (_unacknowledgedPublishPackets) + { + return _unacknowledgedPublishPackets.FirstOrDefault(p => p.PacketIdentifier.Equals(packetIdentifier)); } + } - public void RemoveSubscribedTopic(string topic) + public void Recover() + { + // TODO: Keep the bus and only insert pending items again. + // TODO: Check if packet identifier must be restarted or not. + // TODO: Recover package identifier. + + /* + The Session state in the Client consists of: + · QoS 1 and QoS 2 messages which have been sent to the Server, but have not been completely acknowledged. + · QoS 2 messages which have been received from the Server, but have not been completely acknowledged. + + The Session state in the Server consists of: + · The existence of a Session, even if the rest of the Session state is empty. + · The Client’s subscriptions. + · 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. + · QoS 2 messages which have been received from the Client, but have not been completely acknowledged. + · Optionally, QoS 0 messages pending transmission to the Client. + */ + + // Create a copy of all currently unacknowledged publish packets and clear the storage. + // We must re-enqueue them in order to trigger other code. + List unacknowledgedPublishPackets; + lock (_unacknowledgedPublishPackets) { - _subscribedTopics?.Remove(topic); + unacknowledgedPublishPackets = _unacknowledgedPublishPackets.ToList(); + _unacknowledgedPublishPackets.Clear(); } - public Task Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + _packetBus.Clear(); + + foreach (var publishPacket in unacknowledgedPublishPackets) { - return _subscriptionsManager.Subscribe(subscribePacket, cancellationToken); + EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); } + } - public bool TryCheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel qualityOfServiceLevel, string senderId, out CheckSubscriptionsResult result) - { - result = null; + public void RemoveSubscribedTopic(string topic) + { + _subscribedTopics?.Remove(topic); + } - try - { - result = _subscriptionsManager.CheckSubscriptions(topic, topicHash, qualityOfServiceLevel, senderId); - return true; - } - catch - { - return false; - } - } + public Task Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + { + return _subscriptionsManager.Subscribe(subscribePacket, cancellationToken); + } - public Task Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + public bool TryCheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel qualityOfServiceLevel, string senderId, out CheckSubscriptionsResult result) + { + result = null; + + try { - return _subscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken); + result = _subscriptionsManager.CheckSubscriptions(topic, topicHash, qualityOfServiceLevel, senderId); + return true; } + catch + { + return false; + } + } + + public Task Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + { + return _subscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken); } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs b/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs index bbb10e430..c28103362 100644 --- a/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs +++ b/Source/MQTTnet.Server/Internal/MqttSessionsStorage.cs @@ -2,12 +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; using System.Collections.ObjectModel; -using System.Linq; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttSessionsStorage { diff --git a/Source/MQTTnet.Server/Internal/MqttSubscription.cs b/Source/MQTTnet.Server/Internal/MqttSubscription.cs index c1117ccb8..95b18cabd 100644 --- a/Source/MQTTnet.Server/Internal/MqttSubscription.cs +++ b/Source/MQTTnet.Server/Internal/MqttSubscription.cs @@ -4,7 +4,7 @@ using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class MqttSubscription { diff --git a/Source/MQTTnet.Server/Internal/MqttTopicHash.cs b/Source/MQTTnet.Server/Internal/MqttTopicHash.cs index a1169f3c9..0e23a9d87 100644 --- a/Source/MQTTnet.Server/Internal/MqttTopicHash.cs +++ b/Source/MQTTnet.Server/Internal/MqttTopicHash.cs @@ -2,7 +2,7 @@ // 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.Internal { /* * The MqttSubscription object stores subscription parameters and calculates diff --git a/Source/MQTTnet.Server/Internal/SubscribeResult.cs b/Source/MQTTnet.Server/Internal/SubscribeResult.cs index 9c828edad..c4d20aa11 100644 --- a/Source/MQTTnet.Server/Internal/SubscribeResult.cs +++ b/Source/MQTTnet.Server/Internal/SubscribeResult.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. -using System.Collections.Generic; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class SubscribeResult { diff --git a/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs b/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs index fa0749950..38f75e50d 100644 --- a/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs +++ b/Source/MQTTnet.Server/Internal/TopicHashMaskSubscriptions.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { /// /// Helper class that stores subscriptions by their topic hash mask. diff --git a/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs b/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs index 38033b197..1c1b34ebd 100644 --- a/Source/MQTTnet.Server/Internal/UnsubscribeResult.cs +++ b/Source/MQTTnet.Server/Internal/UnsubscribeResult.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. -using System.Collections.Generic; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server.Internal { public sealed class UnsubscribeResult { diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj index 7f903d376..6c88bd51c 100644 --- a/Source/MQTTnet.Server/MQTTnet.Server.csproj +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -35,7 +35,7 @@ - + @@ -46,7 +46,7 @@ - + diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj.DotSettings b/Source/MQTTnet.Server/MQTTnet.Server.csproj.DotSettings new file mode 100644 index 000000000..78c5f25c6 --- /dev/null +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj.DotSettings @@ -0,0 +1,12 @@ + + True + True + True + True + True \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttClientDisconnectType.cs b/Source/MQTTnet.Server/MqttClientDisconnectType.cs index 7f738c81b..554bf58e3 100644 --- a/Source/MQTTnet.Server/MqttClientDisconnectType.cs +++ b/Source/MQTTnet.Server/MqttClientDisconnectType.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 MqttClientDisconnectType { - public enum MqttClientDisconnectType - { - Clean, - NotClean, - Takeover - } -} + Clean, + NotClean, + Takeover +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttRetainedMessageMatch.cs b/Source/MQTTnet.Server/MqttRetainedMessageMatch.cs index 59bfbc2c4..7f392cbf2 100644 --- a/Source/MQTTnet.Server/MqttRetainedMessageMatch.cs +++ b/Source/MQTTnet.Server/MqttRetainedMessageMatch.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.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttRetainedMessageMatch { - public sealed class MqttRetainedMessageMatch + public MqttRetainedMessageMatch(MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel) { - public MqttRetainedMessageMatch(MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel) - { - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel; - } + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel; + } - public MqttApplicationMessage ApplicationMessage { get; } + public MqttApplicationMessage ApplicationMessage { get; } - public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; } - } + public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttServer.cs b/Source/MQTTnet.Server/MqttServer.cs index a62f17c7f..0bb0ae0f4 100644 --- a/Source/MQTTnet.Server/MqttServer.cs +++ b/Source/MQTTnet.Server/MqttServer.cs @@ -2,448 +2,441 @@ // 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.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Server.Adapter; -using MQTTnet.Server.Disconnecting; +using MQTTnet.Server.Internal; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public class MqttServer : Disposable { - public class MqttServer : Disposable + readonly ICollection _adapters; + readonly MqttClientSessionsManager _clientSessionsManager; + readonly MqttServerEventContainer _eventContainer = new(); + readonly MqttServerKeepAliveMonitor _keepAliveMonitor; + readonly MqttNetSourceLogger _logger; + readonly MqttServerOptions _options; + readonly MqttRetainedMessagesManager _retainedMessagesManager; + readonly IMqttNetLogger _rootLogger; + + CancellationTokenSource _cancellationTokenSource; + bool _isStopping; + + public MqttServer(MqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) { - readonly ICollection _adapters; - readonly MqttClientSessionsManager _clientSessionsManager; - readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer(); - readonly MqttServerKeepAliveMonitor _keepAliveMonitor; - readonly MqttNetSourceLogger _logger; - readonly MqttServerOptions _options; - readonly MqttRetainedMessagesManager _retainedMessagesManager; - readonly IMqttNetLogger _rootLogger; - - CancellationTokenSource _cancellationTokenSource; - bool _isStopping; + _options = options ?? throw new ArgumentNullException(nameof(options)); - public MqttServer(MqttServerOptions options, IEnumerable adapters, IMqttNetLogger logger) + if (adapters == null) { - _options = options ?? throw new ArgumentNullException(nameof(options)); + throw new ArgumentNullException(nameof(adapters)); + } - if (adapters == null) - { - throw new ArgumentNullException(nameof(adapters)); - } + _adapters = adapters.ToList(); - _adapters = adapters.ToList(); + _rootLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger.WithSource(nameof(MqttServer)); - _rootLogger = logger ?? throw new ArgumentNullException(nameof(logger)); - _logger = logger.WithSource(nameof(MqttServer)); + _retainedMessagesManager = new MqttRetainedMessagesManager(_eventContainer, _rootLogger); + _clientSessionsManager = new MqttClientSessionsManager(options, _retainedMessagesManager, _eventContainer, _rootLogger); + _keepAliveMonitor = new MqttServerKeepAliveMonitor(options, _clientSessionsManager, _rootLogger); + } - _retainedMessagesManager = new MqttRetainedMessagesManager(_eventContainer, _rootLogger); - _clientSessionsManager = new MqttClientSessionsManager(options, _retainedMessagesManager, _eventContainer, _rootLogger); - _keepAliveMonitor = new MqttServerKeepAliveMonitor(options, _clientSessionsManager, _rootLogger); - } + public event Func ApplicationMessageEnqueuedOrDroppedAsync + { + add => _eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.AddHandler(value); + remove => _eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.RemoveHandler(value); + } - public event Func ApplicationMessageEnqueuedOrDroppedAsync - { - add => _eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.AddHandler(value); - remove => _eventContainer.ApplicationMessageEnqueuedOrDroppedEvent.RemoveHandler(value); - } + public event Func ApplicationMessageNotConsumedAsync + { + add => _eventContainer.ApplicationMessageNotConsumedEvent.AddHandler(value); + remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value); + } - public event Func ApplicationMessageNotConsumedAsync - { - add => _eventContainer.ApplicationMessageNotConsumedEvent.AddHandler(value); - remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value); - } + public event Func ClientAcknowledgedPublishPacketAsync + { + add => _eventContainer.ClientAcknowledgedPublishPacketEvent.AddHandler(value); + remove => _eventContainer.ClientAcknowledgedPublishPacketEvent.RemoveHandler(value); + } - public event Func ClientAcknowledgedPublishPacketAsync - { - add => _eventContainer.ClientAcknowledgedPublishPacketEvent.AddHandler(value); - remove => _eventContainer.ClientAcknowledgedPublishPacketEvent.RemoveHandler(value); - } + public event Func ClientConnectedAsync + { + add => _eventContainer.ClientConnectedEvent.AddHandler(value); + remove => _eventContainer.ClientConnectedEvent.RemoveHandler(value); + } - public event Func ClientConnectedAsync - { - add => _eventContainer.ClientConnectedEvent.AddHandler(value); - remove => _eventContainer.ClientConnectedEvent.RemoveHandler(value); - } + public event Func ClientDisconnectedAsync + { + add => _eventContainer.ClientDisconnectedEvent.AddHandler(value); + remove => _eventContainer.ClientDisconnectedEvent.RemoveHandler(value); + } - public event Func ClientDisconnectedAsync - { - add => _eventContainer.ClientDisconnectedEvent.AddHandler(value); - remove => _eventContainer.ClientDisconnectedEvent.RemoveHandler(value); - } + public event Func ClientSubscribedTopicAsync + { + add => _eventContainer.ClientSubscribedTopicEvent.AddHandler(value); + remove => _eventContainer.ClientSubscribedTopicEvent.RemoveHandler(value); + } - public event Func ClientSubscribedTopicAsync - { - add => _eventContainer.ClientSubscribedTopicEvent.AddHandler(value); - remove => _eventContainer.ClientSubscribedTopicEvent.RemoveHandler(value); - } + public event Func ClientUnsubscribedTopicAsync + { + add => _eventContainer.ClientUnsubscribedTopicEvent.AddHandler(value); + remove => _eventContainer.ClientUnsubscribedTopicEvent.RemoveHandler(value); + } - public event Func ClientUnsubscribedTopicAsync - { - add => _eventContainer.ClientUnsubscribedTopicEvent.AddHandler(value); - remove => _eventContainer.ClientUnsubscribedTopicEvent.RemoveHandler(value); - } + public event Func InterceptingClientEnqueueAsync + { + add => _eventContainer.InterceptingClientEnqueueEvent.AddHandler(value); + remove => _eventContainer.InterceptingClientEnqueueEvent.RemoveHandler(value); + } - public event Func InterceptingClientEnqueueAsync - { - add => _eventContainer.InterceptingClientEnqueueEvent.AddHandler(value); - remove => _eventContainer.InterceptingClientEnqueueEvent.RemoveHandler(value); - } + public event Func InterceptingInboundPacketAsync + { + add => _eventContainer.InterceptingInboundPacketEvent.AddHandler(value); + remove => _eventContainer.InterceptingInboundPacketEvent.RemoveHandler(value); + } - public event Func InterceptingInboundPacketAsync - { - add => _eventContainer.InterceptingInboundPacketEvent.AddHandler(value); - remove => _eventContainer.InterceptingInboundPacketEvent.RemoveHandler(value); - } + public event Func InterceptingOutboundPacketAsync + { + add => _eventContainer.InterceptingOutboundPacketEvent.AddHandler(value); + remove => _eventContainer.InterceptingOutboundPacketEvent.RemoveHandler(value); + } - public event Func InterceptingOutboundPacketAsync - { - add => _eventContainer.InterceptingOutboundPacketEvent.AddHandler(value); - remove => _eventContainer.InterceptingOutboundPacketEvent.RemoveHandler(value); - } + public event Func InterceptingPublishAsync + { + add => _eventContainer.InterceptingPublishEvent.AddHandler(value); + remove => _eventContainer.InterceptingPublishEvent.RemoveHandler(value); + } - public event Func InterceptingPublishAsync - { - add => _eventContainer.InterceptingPublishEvent.AddHandler(value); - remove => _eventContainer.InterceptingPublishEvent.RemoveHandler(value); - } + public event Func InterceptingSubscriptionAsync + { + add => _eventContainer.InterceptingSubscriptionEvent.AddHandler(value); + remove => _eventContainer.InterceptingSubscriptionEvent.RemoveHandler(value); + } - public event Func InterceptingSubscriptionAsync - { - add => _eventContainer.InterceptingSubscriptionEvent.AddHandler(value); - remove => _eventContainer.InterceptingSubscriptionEvent.RemoveHandler(value); - } + public event Func InterceptingUnsubscriptionAsync + { + add => _eventContainer.InterceptingUnsubscriptionEvent.AddHandler(value); + remove => _eventContainer.InterceptingUnsubscriptionEvent.RemoveHandler(value); + } - public event Func InterceptingUnsubscriptionAsync - { - add => _eventContainer.InterceptingUnsubscriptionEvent.AddHandler(value); - remove => _eventContainer.InterceptingUnsubscriptionEvent.RemoveHandler(value); - } + public event Func LoadingRetainedMessageAsync + { + add => _eventContainer.LoadingRetainedMessagesEvent.AddHandler(value); + remove => _eventContainer.LoadingRetainedMessagesEvent.RemoveHandler(value); + } - public event Func LoadingRetainedMessageAsync - { - add => _eventContainer.LoadingRetainedMessagesEvent.AddHandler(value); - remove => _eventContainer.LoadingRetainedMessagesEvent.RemoveHandler(value); - } + public event Func PreparingSessionAsync + { + add => _eventContainer.PreparingSessionEvent.AddHandler(value); + remove => _eventContainer.PreparingSessionEvent.RemoveHandler(value); + } - public event Func PreparingSessionAsync - { - add => _eventContainer.PreparingSessionEvent.AddHandler(value); - remove => _eventContainer.PreparingSessionEvent.RemoveHandler(value); - } + public event Func QueuedApplicationMessageOverwrittenAsync + { + add => _eventContainer.QueuedApplicationMessageOverwrittenEvent.AddHandler(value); + remove => _eventContainer.QueuedApplicationMessageOverwrittenEvent.RemoveHandler(value); + } - public event Func QueuedApplicationMessageOverwrittenAsync - { - add => _eventContainer.QueuedApplicationMessageOverwrittenEvent.AddHandler(value); - remove => _eventContainer.QueuedApplicationMessageOverwrittenEvent.RemoveHandler(value); - } + public event Func RetainedMessageChangedAsync + { + add => _eventContainer.RetainedMessageChangedEvent.AddHandler(value); + remove => _eventContainer.RetainedMessageChangedEvent.RemoveHandler(value); + } - public event Func RetainedMessageChangedAsync - { - add => _eventContainer.RetainedMessageChangedEvent.AddHandler(value); - remove => _eventContainer.RetainedMessageChangedEvent.RemoveHandler(value); - } + public event Func RetainedMessagesClearedAsync + { + add => _eventContainer.RetainedMessagesClearedEvent.AddHandler(value); + remove => _eventContainer.RetainedMessagesClearedEvent.RemoveHandler(value); + } - public event Func RetainedMessagesClearedAsync - { - add => _eventContainer.RetainedMessagesClearedEvent.AddHandler(value); - remove => _eventContainer.RetainedMessagesClearedEvent.RemoveHandler(value); - } + public event Func SessionDeletedAsync + { + add => _eventContainer.SessionDeletedEvent.AddHandler(value); + remove => _eventContainer.SessionDeletedEvent.RemoveHandler(value); + } - public event Func SessionDeletedAsync - { - add => _eventContainer.SessionDeletedEvent.AddHandler(value); - remove => _eventContainer.SessionDeletedEvent.RemoveHandler(value); - } + public event Func StartedAsync + { + add => _eventContainer.StartedEvent.AddHandler(value); + remove => _eventContainer.StartedEvent.RemoveHandler(value); + } - public event Func StartedAsync - { - add => _eventContainer.StartedEvent.AddHandler(value); - remove => _eventContainer.StartedEvent.RemoveHandler(value); - } + public event Func StoppedAsync + { + add => _eventContainer.StoppedEvent.AddHandler(value); + remove => _eventContainer.StoppedEvent.RemoveHandler(value); + } + + public event Func ValidatingConnectionAsync + { + add => _eventContainer.ValidatingConnectionEvent.AddHandler(value); + remove => _eventContainer.ValidatingConnectionEvent.RemoveHandler(value); + } + + /// + /// Gets or sets whether the server will accept new connections. + /// If not, the server will close the connection without any notification (DISCONNECT packet). + /// This feature can be used when the server is shutting down. + /// + public bool AcceptNewConnections { get; set; } = true; + + public bool IsStarted => _cancellationTokenSource != null; + + /// + /// Gives access to the session items which belong to this server. This session items are passed + /// to several events instead of the client session items if the event is caused by the server instead of a client. + /// + public IDictionary ServerSessionItems { get; } = new ConcurrentDictionary(); - public event Func StoppedAsync + public Task DeleteRetainedMessagesAsync() + { + ThrowIfNotStarted(); + + return _retainedMessagesManager?.ClearMessages() ?? CompletedTask.Instance; + } + + public Task DisconnectClientAsync(string id, MqttServerClientDisconnectOptions options) + { + if (id == null) { - add => _eventContainer.StoppedEvent.AddHandler(value); - remove => _eventContainer.StoppedEvent.RemoveHandler(value); + throw new ArgumentNullException(nameof(id)); } - public event Func ValidatingConnectionAsync + if (options == null) { - add => _eventContainer.ValidatingConnectionEvent.AddHandler(value); - remove => _eventContainer.ValidatingConnectionEvent.RemoveHandler(value); + throw new ArgumentNullException(nameof(options)); } - /// - /// Gets or sets whether the server will accept new connections. - /// If not, the server will close the connection without any notification (DISCONNECT packet). - /// This feature can be used when the server is shutting down. - /// - public bool AcceptNewConnections { get; set; } = true; + ThrowIfNotStarted(); - public bool IsStarted => _cancellationTokenSource != null; + return _clientSessionsManager.GetClient(id).StopAsync(options); + } - /// - /// Gives access to the session items which belong to this server. This session items are passed - /// to several events instead of the client session items if the event is caused by the server instead of a client. - /// - public IDictionary ServerSessionItems { get; } = new ConcurrentDictionary(); + public Task> GetClientsAsync() + { + ThrowIfNotStarted(); - public Task DeleteRetainedMessagesAsync() - { - ThrowIfNotStarted(); + return _clientSessionsManager.GetClientsStatus(); + } - return _retainedMessagesManager?.ClearMessages() ?? CompletedTask.Instance; + public Task GetRetainedMessageAsync(string topic) + { + if (topic == null) + { + throw new ArgumentNullException(nameof(topic)); } - public Task DisconnectClientAsync(string id, MqttServerClientDisconnectOptions options) - { - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } + ThrowIfNotStarted(); - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + return _retainedMessagesManager.GetMessage(topic); + } - ThrowIfNotStarted(); + public Task> GetRetainedMessagesAsync() + { + ThrowIfNotStarted(); - return _clientSessionsManager.GetClient(id).StopAsync(options); - } + return _retainedMessagesManager.GetMessages(); + } - public Task> GetClientsAsync() - { - ThrowIfNotStarted(); + public Task> GetSessionsAsync() + { + ThrowIfNotStarted(); - return _clientSessionsManager.GetClientsStatus(); - } + return _clientSessionsManager.GetSessionsStatus(); + } - public Task GetRetainedMessageAsync(string topic) + public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default) + { + if (injectedApplicationMessage == null) { - if (topic == null) - { - throw new ArgumentNullException(nameof(topic)); - } - - ThrowIfNotStarted(); - - return _retainedMessagesManager.GetMessage(topic); + throw new ArgumentNullException(nameof(injectedApplicationMessage)); } - public Task> GetRetainedMessagesAsync() + if (injectedApplicationMessage.ApplicationMessage == null) { - ThrowIfNotStarted(); - - return _retainedMessagesManager.GetMessages(); + throw new ArgumentNullException(nameof(injectedApplicationMessage.ApplicationMessage)); } - public Task> GetSessionsAsync() - { - ThrowIfNotStarted(); + MqttTopicValidator.ThrowIfInvalid(injectedApplicationMessage.ApplicationMessage.Topic); - return _clientSessionsManager.GetSessionsStatus(); - } + ThrowIfNotStarted(); - public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default) + if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic)) { - if (injectedApplicationMessage == null) - { - throw new ArgumentNullException(nameof(injectedApplicationMessage)); - } + throw new NotSupportedException("Injected application messages must contain a topic (topic alias is not supported)"); + } - if (injectedApplicationMessage.ApplicationMessage == null) - { - throw new ArgumentNullException(nameof(injectedApplicationMessage.ApplicationMessage)); - } + var sessionItems = injectedApplicationMessage.CustomSessionItems ?? ServerSessionItems; - MqttTopicValidator.ThrowIfInvalid(injectedApplicationMessage.ApplicationMessage.Topic); + return _clientSessionsManager.DispatchApplicationMessage( + injectedApplicationMessage.SenderClientId, + sessionItems, + injectedApplicationMessage.ApplicationMessage, + cancellationToken); + } - ThrowIfNotStarted(); + public async Task StartAsync() + { + ThrowIfStarted(); - if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic)) - { - throw new NotSupportedException("Injected application messages must contain a topic (topic alias is not supported)"); - } + _isStopping = false; - var sessionItems = injectedApplicationMessage.CustomSessionItems ?? ServerSessionItems; + _cancellationTokenSource = new CancellationTokenSource(); + var cancellationToken = _cancellationTokenSource.Token; - return _clientSessionsManager.DispatchApplicationMessage( - injectedApplicationMessage.SenderClientId, - sessionItems, - injectedApplicationMessage.ApplicationMessage, - cancellationToken); - } + await _retainedMessagesManager.Start().ConfigureAwait(false); + _clientSessionsManager.Start(); + _keepAliveMonitor.Start(cancellationToken); - public async Task StartAsync() + foreach (var adapter in _adapters) { - ThrowIfStarted(); - - _isStopping = false; - - _cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = _cancellationTokenSource.Token; - - await _retainedMessagesManager.Start().ConfigureAwait(false); - _clientSessionsManager.Start(); - _keepAliveMonitor.Start(cancellationToken); + adapter.ClientHandler = c => OnHandleClient(c, cancellationToken); + await adapter.StartAsync(_options, _rootLogger).ConfigureAwait(false); + } - foreach (var adapter in _adapters) - { - adapter.ClientHandler = c => OnHandleClient(c, cancellationToken); - await adapter.StartAsync(_options, _rootLogger).ConfigureAwait(false); - } + await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); - await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); + _logger.Info("Started"); + } - _logger.Info("Started"); + public async Task StopAsync(MqttServerStopOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); } - public async Task StopAsync(MqttServerStopOptions options) + try { - if (options == null) + if (_cancellationTokenSource == null) { - throw new ArgumentNullException(nameof(options)); + return; } - try - { - if (_cancellationTokenSource == null) - { - return; - } - - _isStopping = true; + _isStopping = true; - _cancellationTokenSource.Cancel(false); + _cancellationTokenSource.Cancel(false); - await _clientSessionsManager.CloseAllConnections(options.DefaultClientDisconnectOptions).ConfigureAwait(false); + await _clientSessionsManager.CloseAllConnections(options.DefaultClientDisconnectOptions).ConfigureAwait(false); - foreach (var adapter in _adapters) - { - adapter.ClientHandler = null; - await adapter.StopAsync().ConfigureAwait(false); - } - } - finally + foreach (var adapter in _adapters) { - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; + adapter.ClientHandler = null; + await adapter.StopAsync().ConfigureAwait(false); } - - await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); - - _logger.Info("Stopped"); } - - public Task SubscribeAsync(string clientId, ICollection topicFilters) + finally { - if (clientId == null) - { - throw new ArgumentNullException(nameof(clientId)); - } + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } - if (topicFilters == null) - { - throw new ArgumentNullException(nameof(topicFilters)); - } + await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); - foreach (var topicFilter in topicFilters) - { - MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic); - } + _logger.Info("Stopped"); + } - ThrowIfDisposed(); - ThrowIfNotStarted(); + public Task SubscribeAsync(string clientId, ICollection topicFilters) + { + if (clientId == null) + { + throw new ArgumentNullException(nameof(clientId)); + } - return _clientSessionsManager.SubscribeAsync(clientId, topicFilters); + if (topicFilters == null) + { + throw new ArgumentNullException(nameof(topicFilters)); } - public Task UnsubscribeAsync(string clientId, ICollection topicFilters) + foreach (var topicFilter in topicFilters) { - if (clientId == null) - { - throw new ArgumentNullException(nameof(clientId)); - } + MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic); + } - if (topicFilters == null) - { - throw new ArgumentNullException(nameof(topicFilters)); - } + ThrowIfDisposed(); + ThrowIfNotStarted(); - ThrowIfDisposed(); - ThrowIfNotStarted(); + return _clientSessionsManager.SubscribeAsync(clientId, topicFilters); + } - return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); + public Task UnsubscribeAsync(string clientId, ICollection topicFilters) + { + if (clientId == null) + { + throw new ArgumentNullException(nameof(clientId)); } - public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage) + if (topicFilters == null) { - if (retainedMessage == null) - { - throw new ArgumentNullException(nameof(retainedMessage)); - } + throw new ArgumentNullException(nameof(topicFilters)); + } - ThrowIfDisposed(); - ThrowIfNotStarted(); + ThrowIfDisposed(); + ThrowIfNotStarted(); - return _retainedMessagesManager?.UpdateMessage(string.Empty, retainedMessage); - } + return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); + } - protected override void Dispose(bool disposing) + public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage) + { + if (retainedMessage == null) { - if (disposing) - { - StopAsync(new MqttServerStopOptions()).GetAwaiter().GetResult(); + throw new ArgumentNullException(nameof(retainedMessage)); + } - foreach (var adapter in _adapters) - { - adapter.Dispose(); - } - } + ThrowIfDisposed(); + ThrowIfNotStarted(); - base.Dispose(disposing); - } + return _retainedMessagesManager?.UpdateMessage(string.Empty, retainedMessage); + } - Task OnHandleClient(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + protected override void Dispose(bool disposing) + { + if (disposing) { - if (_isStopping || !AcceptNewConnections) + StopAsync(new MqttServerStopOptions()).GetAwaiter().GetResult(); + + foreach (var adapter in _adapters) { - return CompletedTask.Instance; + adapter.Dispose(); } - - return _clientSessionsManager.HandleClientConnectionAsync(channelAdapter, cancellationToken); } - void ThrowIfNotStarted() - { - ThrowIfDisposed(); + base.Dispose(disposing); + } - if (_cancellationTokenSource == null) - { - throw new InvalidOperationException("The MQTT server is not started."); - } + Task OnHandleClient(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + { + if (_isStopping || !AcceptNewConnections) + { + return CompletedTask.Instance; } - void ThrowIfStarted() + return _clientSessionsManager.HandleClientConnectionAsync(channelAdapter, cancellationToken); + } + + void ThrowIfNotStarted() + { + ThrowIfDisposed(); + + if (_cancellationTokenSource == null) { - ThrowIfDisposed(); + throw new InvalidOperationException("The MQTT server is not started."); + } + } - if (_cancellationTokenSource != null) - { - throw new InvalidOperationException("The MQTT server is already started."); - } + void ThrowIfStarted() + { + ThrowIfDisposed(); + + if (_cancellationTokenSource != null) + { + throw new InvalidOperationException("The MQTT server is already started."); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttServerExtensions.cs b/Source/MQTTnet.Server/MqttServerExtensions.cs index 33284f4ac..59aecebbf 100644 --- a/Source/MQTTnet.Server/MqttServerExtensions.cs +++ b/Source/MQTTnet.Server/MqttServerExtensions.cs @@ -2,111 +2,107 @@ // 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.Text; -using System.Threading.Tasks; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public static class MqttServerExtensions { - public static class MqttServerExtensions + public static Task DisconnectClientAsync(this MqttServer server, string id, MqttDisconnectReasonCode reasonCode = MqttDisconnectReasonCode.NormalDisconnection) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + + return server.DisconnectClientAsync(id, new MqttServerClientDisconnectOptions { ReasonCode = reasonCode }); + } + + public static Task InjectApplicationMessage( + this MqttServer server, + string topic, + string payload = null, + MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, + bool retain = false) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + + if (topic == null) + { + throw new ArgumentNullException(nameof(topic)); + } + + var payloadBuffer = EmptyBuffer.Array; + if (payload is string stringPayload) + { + payloadBuffer = Encoding.UTF8.GetBytes(stringPayload); + } + + return server.InjectApplicationMessage( + new InjectedMqttApplicationMessage( + new MqttApplicationMessage + { + Topic = topic, + PayloadSegment = new ArraySegment(payloadBuffer), + QualityOfServiceLevel = qualityOfServiceLevel, + Retain = retain + })); + } + + public static Task StopAsync(this MqttServer server) { - public static Task DisconnectClientAsync(this MqttServer server, string id, MqttDisconnectReasonCode reasonCode = MqttDisconnectReasonCode.NormalDisconnection) + if (server == null) { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } + throw new ArgumentNullException(nameof(server)); + } + + return server.StopAsync(new MqttServerStopOptions()); + } - return server.DisconnectClientAsync(id, new MqttServerClientDisconnectOptions { ReasonCode = reasonCode }); + public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); } - public static Task InjectApplicationMessage( - this MqttServer server, - string topic, - string payload = null, - MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, - bool retain = false) + if (clientId == null) { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } - - if (topic == null) - { - throw new ArgumentNullException(nameof(topic)); - } - - var payloadBuffer = EmptyBuffer.Array; - if (payload is string stringPayload) - { - payloadBuffer = Encoding.UTF8.GetBytes(stringPayload); - } - - return server.InjectApplicationMessage( - new InjectedMqttApplicationMessage( - new MqttApplicationMessage - { - Topic = topic, - PayloadSegment = new ArraySegment(payloadBuffer), - QualityOfServiceLevel = qualityOfServiceLevel, - Retain = retain - })); + throw new ArgumentNullException(nameof(clientId)); } - public static Task StopAsync(this MqttServer server) + if (topicFilters == null) { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } + throw new ArgumentNullException(nameof(topicFilters)); + } + + return server.SubscribeAsync(clientId, topicFilters); + } - return server.StopAsync(new MqttServerStopOptions()); + public static Task SubscribeAsync(this MqttServer server, string clientId, string topic) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); } - public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters) + if (clientId == null) { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } - - if (clientId == null) - { - throw new ArgumentNullException(nameof(clientId)); - } - - if (topicFilters == null) - { - throw new ArgumentNullException(nameof(topicFilters)); - } - - return server.SubscribeAsync(clientId, topicFilters); + throw new ArgumentNullException(nameof(clientId)); } - public static Task SubscribeAsync(this MqttServer server, string clientId, string topic) + if (topic == null) { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } - - if (clientId == null) - { - throw new ArgumentNullException(nameof(clientId)); - } - - if (topic == null) - { - throw new ArgumentNullException(nameof(topic)); - } - - var topicFilters = new MqttTopicFilterBuilder().WithTopic(topic).Build(); - return server.SubscribeAsync(clientId, topicFilters); + throw new ArgumentNullException(nameof(topic)); } + + var topicFilters = new MqttTopicFilterBuilder().WithTopic(topic).Build(); + return server.SubscribeAsync(clientId, topicFilters); } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttServerFactory.cs b/Source/MQTTnet.Server/MqttServerFactory.cs index 1375870c4..fa46614ed 100644 --- a/Source/MQTTnet.Server/MqttServerFactory.cs +++ b/Source/MQTTnet.Server/MqttServerFactory.cs @@ -3,8 +3,7 @@ // See the LICENSE file in the project root for more information. using MQTTnet.Diagnostics; -using MQTTnet.Server.Adapter; -using MQTTnet.Server.Disconnecting; +using MQTTnet.Server.Internal.Adapter; namespace MQTTnet.Server; diff --git a/Source/MQTTnet.Server/PublishResponse.cs b/Source/MQTTnet.Server/PublishResponse.cs index 049861737..4298baf51 100644 --- a/Source/MQTTnet.Server/PublishResponse.cs +++ b/Source/MQTTnet.Server/PublishResponse.cs @@ -2,18 +2,16 @@ // 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 MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class PublishResponse { - public sealed class PublishResponse - { - public MqttPubAckReasonCode ReasonCode { get; set; } = MqttPubAckReasonCode.Success; - - public string ReasonString { get; set; } + public MqttPubAckReasonCode ReasonCode { get; set; } = MqttPubAckReasonCode.Success; + + public string ReasonString { get; set; } - public List UserProperties { get; set; } - } + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Status/MqttClientStatus.cs b/Source/MQTTnet.Server/Status/MqttClientStatus.cs index b26bad7fd..e8b728ed6 100644 --- a/Source/MQTTnet.Server/Status/MqttClientStatus.cs +++ b/Source/MQTTnet.Server/Status/MqttClientStatus.cs @@ -2,67 +2,64 @@ // 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.Tasks; using MQTTnet.Formatter; -using MQTTnet.Server.Disconnecting; +using MQTTnet.Server.Internal; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttClientStatus { - public sealed class MqttClientStatus - { - readonly MqttClient _client; + readonly MqttClient _client; - public MqttClientStatus(MqttClient client) - { - _client = client ?? throw new ArgumentNullException(nameof(client)); - } + public MqttClientStatus(MqttClient client) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + } - public long BytesReceived => _client.ChannelAdapter.BytesReceived; + public long BytesReceived => _client.ChannelAdapter.BytesReceived; - public long BytesSent => _client.ChannelAdapter.BytesSent; + public long BytesSent => _client.ChannelAdapter.BytesSent; - public DateTime ConnectedTimestamp => _client.Statistics.ConnectedTimestamp; + public DateTime ConnectedTimestamp => _client.Statistics.ConnectedTimestamp; - public string Endpoint => _client.Endpoint; + public string Endpoint => _client.Endpoint; - /// - /// Gets or sets the client identifier. - /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. - /// - public string Id => _client.Id; + /// + /// Gets or sets the client identifier. + /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. + /// + public string Id => _client.Id; - public DateTime LastNonKeepAlivePacketReceivedTimestamp => _client.Statistics.LastNonKeepAlivePacketReceivedTimestamp; + public DateTime LastNonKeepAlivePacketReceivedTimestamp => _client.Statistics.LastNonKeepAlivePacketReceivedTimestamp; - public DateTime LastPacketReceivedTimestamp => _client.Statistics.LastPacketReceivedTimestamp; + public DateTime LastPacketReceivedTimestamp => _client.Statistics.LastPacketReceivedTimestamp; - public DateTime LastPacketSentTimestamp => _client.Statistics.LastPacketSentTimestamp; + public DateTime LastPacketSentTimestamp => _client.Statistics.LastPacketSentTimestamp; - public MqttProtocolVersion ProtocolVersion => _client.ChannelAdapter.PacketFormatterAdapter.ProtocolVersion; + public MqttProtocolVersion ProtocolVersion => _client.ChannelAdapter.PacketFormatterAdapter.ProtocolVersion; - public long ReceivedApplicationMessagesCount => _client.Statistics.ReceivedApplicationMessagesCount; + public long ReceivedApplicationMessagesCount => _client.Statistics.ReceivedApplicationMessagesCount; - public long ReceivedPacketsCount => _client.Statistics.ReceivedPacketsCount; + public long ReceivedPacketsCount => _client.Statistics.ReceivedPacketsCount; - public long SentApplicationMessagesCount => _client.Statistics.SentApplicationMessagesCount; + public long SentApplicationMessagesCount => _client.Statistics.SentApplicationMessagesCount; - public long SentPacketsCount => _client.Statistics.SentPacketsCount; + public long SentPacketsCount => _client.Statistics.SentPacketsCount; - public MqttSessionStatus Session { get; set; } + public MqttSessionStatus Session { get; set; } - public Task DisconnectAsync(MqttServerClientDisconnectOptions options) + public Task DisconnectAsync(MqttServerClientDisconnectOptions options) + { + if (options == null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - return _client.StopAsync(options); + throw new ArgumentNullException(nameof(options)); } - public void ResetStatistics() - { - _client.ResetStatistics(); - } + return _client.StopAsync(options); + } + + public void ResetStatistics() + { + _client.ResetStatistics(); } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Status/MqttClientStatusExtensions.cs b/Source/MQTTnet.Server/Status/MqttClientStatusExtensions.cs index d235b2dd3..81e64122b 100644 --- a/Source/MQTTnet.Server/Status/MqttClientStatusExtensions.cs +++ b/Source/MQTTnet.Server/Status/MqttClientStatusExtensions.cs @@ -2,31 +2,27 @@ // 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.Tasks; using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public static class MqttClientStatusExtensions { - public static class MqttClientStatusExtensions + static readonly MqttServerClientDisconnectOptions DefaultDisconnectOptions = new() { - static readonly MqttServerClientDisconnectOptions DefaultDisconnectOptions = new MqttServerClientDisconnectOptions - { - ReasonCode = MqttDisconnectReasonCode.NormalDisconnection, - ReasonString = null, - UserProperties = null, - ServerReference = null - }; + ReasonCode = MqttDisconnectReasonCode.NormalDisconnection, + ReasonString = null, + UserProperties = null, + ServerReference = null + }; - public static Task DisconnectAsync(this MqttClientStatus clientStatus) + public static Task DisconnectAsync(this MqttClientStatus clientStatus) + { + if (clientStatus == null) { - if (clientStatus == null) - { - throw new ArgumentNullException(nameof(clientStatus)); - } - - return clientStatus.DisconnectAsync(DefaultDisconnectOptions); + throw new ArgumentNullException(nameof(clientStatus)); } + + return clientStatus.DisconnectAsync(DefaultDisconnectOptions); } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Status/MqttSessionStatus.cs b/Source/MQTTnet.Server/Status/MqttSessionStatus.cs index 9ba6692f7..f0ed176d1 100644 --- a/Source/MQTTnet.Server/Status/MqttSessionStatus.cs +++ b/Source/MQTTnet.Server/Status/MqttSessionStatus.cs @@ -2,68 +2,66 @@ // 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.Tasks; -using MQTTnet.Formatter; using MQTTnet.Internal; +using MQTTnet.Server.Internal; +using MQTTnet.Server.Internal.Formatter; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class MqttSessionStatus { - public sealed class MqttSessionStatus + readonly MqttSession _session; + + public MqttSessionStatus(MqttSession session) { - readonly MqttSession _session; + _session = session ?? throw new ArgumentNullException(nameof(session)); + } - public MqttSessionStatus(MqttSession session) - { - _session = session ?? throw new ArgumentNullException(nameof(session)); - } + public DateTime CreatedTimestamp => _session.CreatedTimestamp; - public DateTime CreatedTimestamp => _session.CreatedTimestamp; + public DateTime? DisconnectedTimestamp => _session.DisconnectedTimestamp; - public DateTime? DisconnectedTimestamp => _session.DisconnectedTimestamp; + public uint ExpiryInterval => _session.ExpiryInterval; - public uint ExpiryInterval => _session.ExpiryInterval; + public string Id => _session.Id; - public string Id => _session.Id; + public IDictionary Items => _session.Items; - public IDictionary Items => _session.Items; + public long PendingApplicationMessagesCount => _session.PendingDataPacketsCount; - public long PendingApplicationMessagesCount => _session.PendingDataPacketsCount; + public Task ClearApplicationMessagesQueueAsync() + { + throw new NotImplementedException(); + } - public Task ClearApplicationMessagesQueueAsync() - { - throw new NotImplementedException(); - } + public Task DeleteAsync() + { + return _session.DeleteAsync(); + } - public Task DeleteAsync() + public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage) + { + if (applicationMessage == null) { - return _session.DeleteAsync(); + throw new ArgumentNullException(nameof(applicationMessage)); } - public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage) - { - if (applicationMessage == null) - { - throw new ArgumentNullException(nameof(applicationMessage)); - } + var packetBusItem = new MqttPacketBusItem(MqttPublishPacketFactory.Create(applicationMessage)); + _session.EnqueueDataPacket(packetBusItem); - var packetBusItem = new MqttPacketBusItem(MqttPacketFactories.Publish.Create(applicationMessage)); - _session.EnqueueDataPacket(packetBusItem); - - return packetBusItem.WaitAsync(); - } + return packetBusItem.WaitAsync(); + } - public Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) + public Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) + { + if (applicationMessage == null) { - if (applicationMessage == null) - { - throw new ArgumentNullException(nameof(applicationMessage)); - } + throw new ArgumentNullException(nameof(applicationMessage)); + } - _session.EnqueueDataPacket(new MqttPacketBusItem(MqttPacketFactories.Publish.Create(applicationMessage))); + _session.EnqueueDataPacket(new MqttPacketBusItem(MqttPublishPacketFactory.Create(applicationMessage))); - return CompletedTask.Instance; - } + return CompletedTask.Instance; } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs b/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs index 7f0b6910d..5bd97b93d 100644 --- a/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs +++ b/Source/MQTTnet.Server/Stopping/MqttServerStopOptions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using MQTTnet.Protocol; -using MQTTnet.Server.Disconnecting; namespace MQTTnet.Server { diff --git a/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs b/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs index 65b3774cb..227f0d967 100644 --- a/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs +++ b/Source/MQTTnet.Server/Stopping/MqttServerStopOptionsBuilder.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using MQTTnet.Server.Disconnecting; namespace MQTTnet.Server { diff --git a/Source/MQTTnet.Server/SubscribeResponse.cs b/Source/MQTTnet.Server/SubscribeResponse.cs index 5b4959af2..bbee16720 100644 --- a/Source/MQTTnet.Server/SubscribeResponse.cs +++ b/Source/MQTTnet.Server/SubscribeResponse.cs @@ -2,23 +2,21 @@ // 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 MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class SubscribeResponse { - public sealed class SubscribeResponse - { - /// - /// Gets or sets the reason code which is sent to the client. - /// The subscription is skipped when the value is not GrantedQoS_. - /// MQTTv5 only. - /// - public MqttSubscribeReasonCode ReasonCode { get; set; } - - public List UserProperties { get; } = new List(); - - public string ReasonString { get; set; } - } + /// + /// Gets or sets the reason code which is sent to the client. + /// The subscription is skipped when the value is not GrantedQoS_. + /// MQTTv5 only. + /// + public MqttSubscribeReasonCode ReasonCode { get; set; } + + public string ReasonString { get; set; } + + public List UserProperties { get; } = new(); } \ No newline at end of file diff --git a/Source/MQTTnet.Server/UnsubscribeResponse.cs b/Source/MQTTnet.Server/UnsubscribeResponse.cs index 990bde514..463f65ca9 100644 --- a/Source/MQTTnet.Server/UnsubscribeResponse.cs +++ b/Source/MQTTnet.Server/UnsubscribeResponse.cs @@ -2,22 +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.Collections.Generic; using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Server +namespace MQTTnet.Server; + +public sealed class UnsubscribeResponse { - public sealed class UnsubscribeResponse - { - /// - /// Gets or sets the reason code which is sent to the client. - /// MQTTv5 only. - /// - public MqttUnsubscribeReasonCode ReasonCode { get; set; } - - public List UserProperties { get; } = new List(); - - public string ReasonString { get; set; } - } + /// + /// Gets or sets the reason code which is sent to the client. + /// MQTTv5 only. + /// + public MqttUnsubscribeReasonCode ReasonCode { get; set; } + + public string ReasonString { get; set; } + + public List UserProperties { get; } = new(); } \ No newline at end of file diff --git a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj index fa4365bea..3298cf669 100644 --- a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj +++ b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj @@ -12,13 +12,13 @@ - + - - - + + + diff --git a/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs b/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs index c4ea4ce30..b4ca73d1c 100644 --- a/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs +++ b/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs @@ -10,43 +10,41 @@ using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Server; -using MQTTnet.Server.Adapter; -namespace MQTTnet.Tests.ASP.Mockups +namespace MQTTnet.Tests.ASP.Mockups; + +public sealed class ConnectionHandlerMockup : IMqttServerAdapter { - public sealed class ConnectionHandlerMockup : IMqttServerAdapter + public Func ClientHandler { get; set; } + public TaskCompletionSource Context { get; } = new(); + + public void Dispose() { - public Func ClientHandler { get; set; } - public TaskCompletionSource Context { get; } = new TaskCompletionSource(); + } - public void Dispose() + public async Task OnConnectedAsync(ConnectionContext connection) + { + try { - } + var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); + var context = new MqttConnectionContext(formatter, connection); + Context.TrySetResult(context); - public async Task OnConnectedAsync(ConnectionContext connection) - { - try - { - var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); - var context = new MqttConnectionContext(formatter, connection); - Context.TrySetResult(context); - - await ClientHandler(context); - } - catch (Exception ex) - { - Context.TrySetException(ex); - } + await ClientHandler(context); } - - public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) + catch (Exception ex) { - return Task.CompletedTask; + Context.TrySetException(ex); } + } - public Task StopAsync() - { - return Task.CompletedTask; - } + public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) + { + return Task.CompletedTask; + } + + public Task StopAsync() + { + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj index c657490bf..4138c1908 100644 --- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj +++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj @@ -10,19 +10,19 @@ - - - - + + + + - - - - - - + + + + + + - + \ No newline at end of file diff --git a/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs b/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs index 4f1f729bb..cb533492e 100644 --- a/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs +++ b/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs @@ -398,7 +398,7 @@ public async Task StartServer(Action confi var options = optionsBuilder.Build(); var server = CreateServer(options); - await server.StartAsync(); + 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; diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index b8056fbb8..eccb324d4 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; using MQTTnet.Client; +using MQTTnet.Formatter; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -60,7 +61,7 @@ public async Task Collect_Messages_In_Disconnected_Session() 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)); + var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false).WithSessionExpiryInterval(60)); await client1.SubscribeAsync("x"); await client1.DisconnectAsync(); await Task.Delay(500); @@ -68,7 +69,7 @@ public async Task Collect_Messages_In_Disconnected_Session() var clientStatus = await server.GetClientsAsync(); Assert.AreEqual(0, clientStatus.Count); - var client2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("b").WithCleanSession(false)); + 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"); @@ -794,7 +795,7 @@ public async Task Send_Long_Body() Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0])); } } - + [TestMethod] public async Task Set_Subscription_At_Server() { diff --git a/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs b/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs index 36032ed76..227a664e8 100644 --- a/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs +++ b/Source/MQTTnet.Tests/Server/MqttRetainedMessageManager_Tests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using MQTTnet.Server.Internal; namespace MQTTnet.Tests.Server { @@ -14,8 +15,8 @@ public sealed class MqttRetainedMessageManager_Tests public async Task MqttRetainedMessageManager_GetUndefinedTopic() { var logger = new Mockups.TestLogger(); - var eventContainer = new MQTTnet.Server.MqttServerEventContainer(); - var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger); + 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; diff --git a/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs b/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs index 233cd1f49..13a8cd9db 100644 --- a/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs +++ b/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs @@ -10,6 +10,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server; +using MQTTnet.Server.Internal; using MQTTnet.Tests.Mockups; namespace MQTTnet.Tests.Server diff --git a/Source/MQTTnet.Tests/Server/QoS_Tests.cs b/Source/MQTTnet.Tests/Server/QoS_Tests.cs index 2b5e59d28..8f52d614b 100644 --- a/Source/MQTTnet.Tests/Server/QoS_Tests.cs +++ b/Source/MQTTnet.Tests/Server/QoS_Tests.cs @@ -10,160 +10,159 @@ using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +[TestClass] +public sealed class QoS_Tests : BaseTestClass { - [TestClass] - public sealed class QoS_Tests : BaseTestClass + [TestMethod] + public async Task Fire_Event_On_Client_Acknowledges_QoS_0() { - [TestMethod] - public async Task Preserve_Message_Order_For_Queued_Messages() + using (var testEnvironment = CreateTestEnvironment()) { - using (var testEnvironment = CreateTestEnvironment()) + var server = await testEnvironment.StartServer(); + + ClientAcknowledgedPublishPacketEventArgs eventArgs = null; + server.ClientAcknowledgedPublishPacketAsync += args => { - 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(); - - await LongTestDelay(); - await LongTestDelay(); - - // Now inject messages which are appended to the queue of the client. - await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - - await server.InjectApplicationMessage("T", "2", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "1", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - - await server.InjectApplicationMessage("T", "4", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "3", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - - await server.InjectApplicationMessage("T", "6", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "5", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - - await server.InjectApplicationMessage("T", "8", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - await server.InjectApplicationMessage("T", "7", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - - await server.InjectApplicationMessage("T", "9", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - - 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); - - await LongTestDelay(); - - 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(); - - payloadSequence = messages.GeneratePayloadSequence(); - Assert.AreEqual("0214365879", payloadSequence); - } + eventArgs = args; + return CompletedTask.Instance; + }; + + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("A"); + + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("A"); + + await LongTestDelay(); + + // 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_0() + } + + [TestMethod] + public async Task Fire_Event_On_Client_Acknowledges_QoS_1() + { + using (var testEnvironment = CreateTestEnvironment()) { - using (var testEnvironment = CreateTestEnvironment()) + var server = await testEnvironment.StartServer(); + + ClientAcknowledgedPublishPacketEventArgs eventArgs = null; + server.ClientAcknowledgedPublishPacketAsync += args => { - var server = await testEnvironment.StartServer(); + 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"); + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("A"); + await LongTestDelay(); - await LongTestDelay(); + Assert.IsNotNull(eventArgs); + Assert.IsNotNull(eventArgs.PublishPacket); + Assert.IsNotNull(eventArgs.AcknowledgePacket); + Assert.IsTrue(eventArgs.IsCompleted); - // Must be null because no event should be fired for QoS 0. - Assert.IsNull(eventArgs); - } + Assert.AreEqual("A", eventArgs.PublishPacket.Topic); } + } - [TestMethod] - public async Task Fire_Event_On_Client_Acknowledges_QoS_1() + [TestMethod] + public async Task Fire_Event_On_Client_Acknowledges_QoS_2() + { + using (var testEnvironment = CreateTestEnvironment()) { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + var server = await testEnvironment.StartServer(); - ClientAcknowledgedPublishPacketEventArgs eventArgs = null; - server.ClientAcknowledgedPublishPacketAsync += args => + var eventArgs = new List(); + server.ClientAcknowledgedPublishPacketAsync += args => + { + lock (eventArgs) { - eventArgs = args; - return CompletedTask.Instance; - }; + eventArgs.Add(args); + } + + return CompletedTask.Instance; + }; + + var client1 = await testEnvironment.ConnectClient(); + await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.ExactlyOnce); + + var client2 = await testEnvironment.ConnectClient(); + await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce); - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtLeastOnce); + await LongTestDelay(); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); + Assert.AreEqual(1, eventArgs.Count); - await LongTestDelay(); + var firstEvent = eventArgs[0]; - Assert.IsNotNull(eventArgs); - Assert.IsNotNull(eventArgs.PublishPacket); - Assert.IsNotNull(eventArgs.AcknowledgePacket); - Assert.IsTrue(eventArgs.IsCompleted); + Assert.IsNotNull(firstEvent); + Assert.IsNotNull(firstEvent.PublishPacket); + Assert.IsNotNull(firstEvent.AcknowledgePacket); + Assert.IsTrue(firstEvent.IsCompleted); - Assert.AreEqual("A", eventArgs.PublishPacket.Topic); - } + Assert.AreEqual("A", firstEvent.PublishPacket.Topic); } + } - [TestMethod] - public async Task Fire_Event_On_Client_Acknowledges_QoS_2() + [TestMethod] + public async Task Preserve_Message_Order_For_Queued_Messages() + { + using (var testEnvironment = CreateTestEnvironment()) { - using (var testEnvironment = CreateTestEnvironment()) - { - var server = await testEnvironment.StartServer(); + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); - var eventArgs = new List(); - server.ClientAcknowledgedPublishPacketAsync += args => - { - lock (eventArgs) - { - eventArgs.Add(args); - } + // 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(); + + // 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", "4", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "3", MqttQualityOfServiceLevel.AtLeastOnce); + + await server.InjectApplicationMessage("T", "6", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "5", MqttQualityOfServiceLevel.AtLeastOnce); - return CompletedTask.Instance; - }; + await server.InjectApplicationMessage("T", "8", MqttQualityOfServiceLevel.AtLeastOnce); + await server.InjectApplicationMessage("T", "7", MqttQualityOfServiceLevel.AtLeastOnce); - var client1 = await testEnvironment.ConnectClient(); - await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.ExactlyOnce); + await server.InjectApplicationMessage("T", "9", MqttQualityOfServiceLevel.AtLeastOnce); - var client2 = await testEnvironment.ConnectClient(); - await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce); + 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); - Assert.AreEqual(1, eventArgs.Count); + await LongTestDelay(); - var firstEvent = eventArgs[0]; + var payloadSequence = messages.GeneratePayloadSequence(); + Assert.AreEqual("0214365879", payloadSequence); - Assert.IsNotNull(firstEvent); - Assert.IsNotNull(firstEvent.PublishPacket); - Assert.IsNotNull(firstEvent.AcknowledgePacket); - Assert.IsTrue(firstEvent.IsCompleted); + // 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(); - Assert.AreEqual("A", firstEvent.PublishPacket.Topic); - } + payloadSequence = messages.GeneratePayloadSequence(); + Assert.AreEqual("0214365879", payloadSequence); } } } \ 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 bec3fbd0f..c58e79d89 100644 --- a/Source/MQTTnet.Tests/Server/Status_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Status_Tests.cs @@ -11,154 +11,191 @@ using MQTTnet.Server; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.Server +namespace MQTTnet.Tests.Server; + +[TestClass] +public sealed class Status_Tests : BaseTestClass { - [TestClass] - public sealed class Status_Tests : BaseTestClass + [TestMethod] + public async Task Disconnect_Client() { - [TestMethod] - public async Task Show_Client_And_Session_Statistics() + using (var testEnvironment = new TestEnvironment(TestContext)) { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); + 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")); - await Task.Delay(500); + await Task.Delay(1000); - var clientStatus = await server.GetClientsAsync(); - var sessionStatus = await server.GetSessionsAsync(); + var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(2, clientStatus.Count); - Assert.AreEqual(2, sessionStatus.Count); + Assert.AreEqual(1, clientStatus.Count); + Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); - Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); - Assert.IsTrue(clientStatus.Any(s => s.Id == c2.Options.ClientId)); + await clientStatus.First().DisconnectAsync(); - await c1.DisconnectAsync(); - await c2.DisconnectAsync(); + await Task.Delay(500); - await Task.Delay(500); + Assert.IsFalse(c1.IsConnected); - clientStatus = await server.GetClientsAsync(); - sessionStatus = await server.GetSessionsAsync(); + clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(0, clientStatus.Count); - Assert.AreEqual(0, sessionStatus.Count); - } + Assert.AreEqual(0, clientStatus.Count); } + } - [TestMethod] - public async Task Disconnect_Client() + [TestMethod] + public async Task Keep_Persistent_Session_Version311() + { + using (var testEnvironment = new TestEnvironment(TestContext)) { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(); + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); + 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 Task.Delay(1000); + await c1.DisconnectAsync(); - var clientStatus = await server.GetClientsAsync(); + await LongTestDelay(); - Assert.AreEqual(1, clientStatus.Count); - Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId)); + var clientStatus = await server.GetClientsAsync(); + var sessionStatus = await server.GetSessionsAsync(); - await clientStatus.First().DisconnectAsync(); + Assert.AreEqual(1, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); - await Task.Delay(500); + await c2.DisconnectAsync(); - Assert.IsFalse(c1.IsConnected); + await LongTestDelay(); - clientStatus = await server.GetClientsAsync(); + clientStatus = await server.GetClientsAsync(); + sessionStatus = await server.GetSessionsAsync(); - Assert.AreEqual(0, clientStatus.Count); - } + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(2, sessionStatus.Count); } + } - [TestMethod] - public async Task Keep_Persistent_Session() + [TestMethod] + public async Task Keep_Persistent_Session_Version500() + { + using (var testEnvironment = new TestEnvironment(TestContext)) { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + var server = await testEnvironment.StartServer(o => o.WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1").WithCleanSession(false)); - var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2").WithCleanSession(false)); + 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)); - await c1.DisconnectAsync(); + // The session expiry interval is mandatory for MQTT5.0.0 in order keep session! + await c1.DisconnectAsync(sessionExpiryInterval: 60); - await Task.Delay(500); + 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(); + // The session expiry interval is mandatory for MQTT5.0.0 in order keep session! + await c2.DisconnectAsync(sessionExpiryInterval: 60); - await Task.Delay(500); + 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 Track_Sent_Application_Messages() + [TestMethod] + public async Task Show_Client_And_Session_Statistics() + { + using (var testEnvironment = new TestEnvironment(TestContext)) { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + var server = await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClient(); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2")); - for (var i = 1; i < 25; i++) - { - await c1.PublishStringAsync("a"); - await Task.Delay(50); + await Task.Delay(500); - var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount); - Assert.AreEqual(0, clientStatus.First().ReceivedApplicationMessagesCount); - } - } + var clientStatus = await server.GetClientsAsync(); + var sessionStatus = await server.GetSessionsAsync(); + + 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)); + + await c1.DisconnectAsync(); + await c2.DisconnectAsync(); + + await Task.Delay(500); + + clientStatus = await server.GetClientsAsync(); + sessionStatus = await server.GetSessionsAsync(); + + Assert.AreEqual(0, clientStatus.Count); + Assert.AreEqual(0, sessionStatus.Count); } + } - [TestMethod] - public async Task Track_Sent_Packets() + [TestMethod] + public async Task Track_Sent_Application_Messages() + { + using (var testEnvironment = new TestEnvironment(TestContext)) { - using (var testEnvironment = new TestEnvironment(TestContext)) + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + + var c1 = await testEnvironment.ConnectClient(); + + for (var i = 1; i < 25; i++) { - var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); + await c1.PublishStringAsync("a"); + await Task.Delay(50); - var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithNoKeepAlive()); + var clientStatus = await server.GetClientsAsync(); + Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount); + Assert.AreEqual(0, clientStatus.First().ReceivedApplicationMessagesCount); + } + } + } - 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); + [TestMethod] + public async Task Track_Sent_Packets() + { + using (var testEnvironment = new TestEnvironment(TestContext)) + { + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - await Task.Delay(500); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithNoKeepAlive()); - var clientStatus = await server.GetClientsAsync(); + 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); + + var clientStatus = await server.GetClientsAsync(); - Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount, "SAMC invalid!"); + Assert.AreEqual(i, clientStatus.First().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.First().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.First().ReceivedPacketsCount, "RPC invalid!"); } } } -} +} \ 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 24df145d0..c067d5105 100644 --- a/Source/MQTTnet.Tests/Server/Subscription_TopicHash_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Subscription_TopicHash_Tests.cs @@ -8,6 +8,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server; +using MQTTnet.Server.Internal; using MQTTnet.Tests.Mockups; namespace MQTTnet.Tests.Server diff --git a/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs b/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs index 6451a077a..61f8987cd 100644 --- a/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs +++ b/Source/MQTTnet.Tests/TopicFilterComparer_Tests.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Server; +using MQTTnet.Server.Internal; namespace MQTTnet.Tests { diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index 7cd7fd1dd..7fe35dfca 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -9,30 +9,28 @@ using MQTTnet.Formatter; using MQTTnet.Packets; -namespace MQTTnet.Adapter -{ - public interface IMqttChannelAdapter : IDisposable - { - string Endpoint { get; } +namespace MQTTnet.Adapter; - bool IsSecureConnection { get; } +public interface IMqttChannelAdapter : IDisposable +{ + long BytesReceived { get; } - X509Certificate2 ClientCertificate { get; } + long BytesSent { get; } - MqttPacketFormatterAdapter PacketFormatterAdapter { get; } + X509Certificate2 ClientCertificate { get; } + string Endpoint { get; } - long BytesSent { get; } + bool IsSecureConnection { get; } - long BytesReceived { get; } + MqttPacketFormatterAdapter PacketFormatterAdapter { get; } - Task ConnectAsync(CancellationToken cancellationToken); + Task ConnectAsync(CancellationToken cancellationToken); - Task DisconnectAsync(CancellationToken cancellationToken); + Task DisconnectAsync(CancellationToken cancellationToken); - Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken); + Task ReceivePacketAsync(CancellationToken cancellationToken); - Task ReceivePacketAsync(CancellationToken cancellationToken); + void ResetStatistics(); - void ResetStatistics(); - } -} + Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs b/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs index 85f7a74f5..26f568b6e 100644 --- a/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs @@ -5,10 +5,9 @@ using MQTTnet.Client; using MQTTnet.Diagnostics; -namespace MQTTnet.Adapter +namespace MQTTnet.Adapter; + +public interface IMqttClientAdapterFactory { - public interface IMqttClientAdapterFactory - { - IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger); - } -} + IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger); +} \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index fcfffc990..85e5b14b3 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -16,177 +16,229 @@ using MQTTnet.Internal; using MQTTnet.Packets; -namespace MQTTnet.Adapter -{ - public sealed class MqttChannelAdapter : Disposable, IMqttChannelAdapter - { - const uint ErrorOperationAborted = 0x800703E3; - const int ReadBufferSize = 4096; +namespace MQTTnet.Adapter; - readonly IMqttChannel _channel; - readonly byte[] _fixedHeaderBuffer = new byte[2]; - readonly MqttNetSourceLogger _logger; - readonly byte[] _singleByteBuffer = new byte[1]; - readonly AsyncLock _syncRoot = new AsyncLock(); +public sealed class MqttChannelAdapter : Disposable, IMqttChannelAdapter +{ + const uint ErrorOperationAborted = 0x800703E3; + const int ReadBufferSize = 4096; - Statistics _statistics; // mutable struct, don't make readonly! + readonly IMqttChannel _channel; + readonly byte[] _fixedHeaderBuffer = new byte[2]; + readonly MqttNetSourceLogger _logger; + readonly byte[] _singleByteBuffer = new byte[1]; + readonly AsyncLock _syncRoot = new(); - public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger) - { - _channel = channel ?? throw new ArgumentNullException(nameof(channel)); + Statistics _statistics; // mutable struct, don't make readonly! - PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); + public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger) + { + _channel = channel ?? throw new ArgumentNullException(nameof(channel)); - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } + PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); - _logger = logger.WithSource(nameof(MqttChannelAdapter)); + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); } - public bool AllowPacketFragmentation { get; set; } = true; + _logger = logger.WithSource(nameof(MqttChannelAdapter)); + } - public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived); + public bool AllowPacketFragmentation { get; set; } = true; - public long BytesSent => Volatile.Read(ref _statistics._bytesSent); + public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived); - public X509Certificate2 ClientCertificate => _channel.ClientCertificate; + public long BytesSent => Volatile.Read(ref _statistics._bytesSent); - public string Endpoint => _channel.Endpoint; + public X509Certificate2 ClientCertificate => _channel.ClientCertificate; - public bool IsSecureConnection => _channel.IsSecureConnection; + public string Endpoint => _channel.Endpoint; - public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } + public bool IsSecureConnection => _channel.IsSecureConnection; - public MqttPacketInspector PacketInspector { get; set; } + public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } - public async Task ConnectAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); + public MqttPacketInspector PacketInspector { get; set; } - try + public async Task ConnectAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + try + { + /* + * We have to implement a small workaround here to support connecting in Xamarin + * with a disabled WiFi network. If the WiFi is disabled the connect method will + * block forever. Even a cancellation token is not supported properly. + */ + + var timeout = new TaskCompletionSource(); + using (cancellationToken.Register(() => timeout.TrySetResult(null))) { - /* - * We have to implement a small workaround here to support connecting in Xamarin - * with a disabled WiFi network. If the WiFi is disabled the connect method will - * block forever. Even a cancellation token is not supported properly. - */ - - var timeout = new TaskCompletionSource(); - using (cancellationToken.Register(() => timeout.TrySetResult(null))) - { - var connectTask = Task.Run( - async () => + var connectTask = Task.Run( + async () => + { + try { - try - { - await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false); - } - catch + await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false); + } + catch + { + // If the timeout is already reached the exception is no longer of interest and + // must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException. + if (!timeout.Task.IsCompleted) { - // If the timeout is already reached the exception is no longer of interest and - // must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException. - if (!timeout.Task.IsCompleted) - { - throw; - } + throw; } - }, - CancellationToken.None); + } + }, + CancellationToken.None); - await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false); + await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false); - if (timeout.Task.IsCompleted && !connectTask.IsCompleted) - { - throw new OperationCanceledException("MQTT connect canceled.", cancellationToken); - } - - // Make sure that the exception from the connect task gets thrown. - await connectTask.ConfigureAwait(false); + if (timeout.Task.IsCompleted && !connectTask.IsCompleted) + { + throw new OperationCanceledException("MQTT connect canceled.", cancellationToken); } + + // Make sure that the exception from the connect task gets thrown. + await connectTask.ConfigureAwait(false); } - catch (Exception exception) + } + catch (Exception exception) + { + if (!WrapAndThrowException(exception)) { - if (!WrapAndThrowException(exception)) - { - throw; - } + throw; } } + } + + public async Task DisconnectAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); - public async Task DisconnectAsync(CancellationToken cancellationToken) + try { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); + await _channel.DisconnectAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) + { + if (!WrapAndThrowException(exception)) + { + throw; + } + } + } - try + public async Task ReceivePacketAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + try + { + var localPacketInspector = PacketInspector; + localPacketInspector?.BeginReceivePacket(); + + ReceivedMqttPacket receivedPacket; + var receivedPacketTask = ReceiveAsync(cancellationToken); + if (receivedPacketTask.IsCompleted) { - await _channel.DisconnectAsync(cancellationToken).ConfigureAwait(false); + receivedPacket = receivedPacketTask.Result; } - catch (Exception exception) + else { - if (!WrapAndThrowException(exception)) - { - throw; - } + receivedPacket = await receivedPacketTask.ConfigureAwait(false); + } + + if (receivedPacket.TotalLength == 0 || cancellationToken.IsCancellationRequested) + { + return null; + } + + if (localPacketInspector != null) + { + await localPacketInspector.EndReceivePacket().ConfigureAwait(false); + } + + Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength); + + if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown) + { + PacketFormatterAdapter.DetectProtocolVersion(receivedPacket); } + + var packet = PacketFormatterAdapter.Decode(receivedPacket); + if (packet == null) + { + throw new MqttProtocolViolationException("Received malformed packet."); + } + + _logger.Verbose("RX ({0} bytes) <<< {1}", receivedPacket.TotalLength, packet); + + return packet; + } + catch (OperationCanceledException) + { } + catch (ObjectDisposedException) + { + } + catch (Exception exception) + { + if (!WrapAndThrowException(exception)) + { + throw; + } + } + + return null; + } - public async Task ReceivePacketAsync(CancellationToken cancellationToken) + public void ResetStatistics() + { + _statistics.Reset(); + } + + public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + // This lock makes sure that multiple threads can send packets at the same time. + // This is required when a disconnect is sent from another thread while the + // worker thread is still sending publish packets etc. + using (await _syncRoot.EnterAsync(cancellationToken).ConfigureAwait(false)) { + // Check for cancellation here again because "WaitAsync" might take some time. cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); try { - var localPacketInspector = PacketInspector; - localPacketInspector?.BeginReceivePacket(); - - ReceivedMqttPacket receivedPacket; - var receivedPacketTask = ReceiveAsync(cancellationToken); - if (receivedPacketTask.IsCompleted) - { - receivedPacket = receivedPacketTask.Result; - } - else - { - receivedPacket = await receivedPacketTask.ConfigureAwait(false); - } - - if (receivedPacket.TotalLength == 0 || cancellationToken.IsCancellationRequested) - { - return null; - } + var packetBuffer = PacketFormatterAdapter.Encode(packet); + var localPacketInspector = PacketInspector; if (localPacketInspector != null) { - await localPacketInspector.EndReceivePacket().ConfigureAwait(false); + await localPacketInspector.BeginSendPacket(packetBuffer).ConfigureAwait(false); } - Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength); + _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); - if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown) + if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation) { - PacketFormatterAdapter.DetectProtocolVersion(receivedPacket); + await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false); } - - var packet = PacketFormatterAdapter.Decode(receivedPacket); - if (packet == null) + else { - throw new MqttProtocolViolationException("Received malformed packet."); + await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); + await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); } - _logger.Verbose("RX ({0} bytes) <<< {1}", receivedPacket.TotalLength, packet); - - return packet; - } - catch (OperationCanceledException) - { - } - catch (ObjectDisposedException) - { + Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length); } catch (Exception exception) { @@ -195,295 +247,242 @@ public async Task ReceivePacketAsync(CancellationToken cancellationT throw; } } - - return null; + finally + { + PacketFormatterAdapter.Cleanup(); + } } + } - public void ResetStatistics() + protected override void Dispose(bool disposing) + { + if (disposing) { - _statistics.Reset(); + _channel.Dispose(); + _syncRoot.Dispose(); } - public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) - { - ThrowIfDisposed(); + base.Dispose(disposing); + } - // This lock makes sure that multiple threads can send packets at the same time. - // This is required when a disconnect is sent from another thread while the - // worker thread is still sending publish packets etc. - using (await _syncRoot.EnterAsync(cancellationToken).ConfigureAwait(false)) - { - // Check for cancellation here again because "WaitAsync" might take some time. - cancellationToken.ThrowIfCancellationRequested(); + async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) + { + var offset = 0; + var multiplier = 128; + var value = initialEncodedByte & 127; + int encodedByte = initialEncodedByte; - try - { - var packetBuffer = PacketFormatterAdapter.Encode(packet); + while ((encodedByte & 128) != 0) + { + offset++; + if (offset > 3) + { + throw new MqttProtocolViolationException("Remaining length is invalid."); + } - var localPacketInspector = PacketInspector; - if (localPacketInspector != null) - { - await localPacketInspector.BeginSendPacket(packetBuffer).ConfigureAwait(false); - } - - _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); + if (cancellationToken.IsCancellationRequested) + { + return 0; + } - if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation) - { - await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false); - } - else - { - await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); - await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); - } + var readCount = await _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false); - Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length); - } - catch (Exception exception) - { - if (!WrapAndThrowException(exception)) - { - throw; - } - } - finally - { - PacketFormatterAdapter.Cleanup(); - } + if (cancellationToken.IsCancellationRequested) + { + return 0; } - } - protected override void Dispose(bool disposing) - { - if (disposing) + if (readCount == 0) { - _channel.Dispose(); - _syncRoot.Dispose(); + return 0; } - base.Dispose(disposing); - } - - async Task ReadBodyLengthAsync(byte initialEncodedByte, CancellationToken cancellationToken) - { - var offset = 0; - var multiplier = 128; - var value = initialEncodedByte & 127; - int encodedByte = initialEncodedByte; + PacketInspector?.FillReceiveBuffer(_singleByteBuffer); - while ((encodedByte & 128) != 0) - { - offset++; - if (offset > 3) - { - throw new MqttProtocolViolationException("Remaining length is invalid."); - } + encodedByte = _singleByteBuffer[0]; - if (cancellationToken.IsCancellationRequested) - { - return 0; - } + value += (encodedByte & 127) * multiplier; + multiplier *= 128; + } - var readCount = await _channel.ReadAsync(_singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false); + return value; + } - if (cancellationToken.IsCancellationRequested) - { - return 0; - } + async Task ReadFixedHeaderAsync(CancellationToken cancellationToken) + { + // The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length. + // So in all cases at least 2 bytes must be read for a complete MQTT packet. + var buffer = _fixedHeaderBuffer; + var totalBytesRead = 0; - if (readCount == 0) - { - return 0; - } + while (totalBytesRead < buffer.Length) + { + // Check two times for cancellation because the call to _ReadAsync_ might block for some time. + if (cancellationToken.IsCancellationRequested) + { + return ReadFixedHeaderResult.Canceled; + } - PacketInspector?.FillReceiveBuffer(_singleByteBuffer); + int bytesRead; + try + { + bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return ReadFixedHeaderResult.Canceled; + } + catch (SocketException) + { + return ReadFixedHeaderResult.ConnectionClosed; + } - encodedByte = _singleByteBuffer[0]; + if (cancellationToken.IsCancellationRequested) + { + return ReadFixedHeaderResult.Canceled; + } - value += (encodedByte & 127) * multiplier; - multiplier *= 128; + if (bytesRead == 0) + { + return ReadFixedHeaderResult.ConnectionClosed; } - return value; + totalBytesRead += bytesRead; } - async Task ReadFixedHeaderAsync(CancellationToken cancellationToken) + PacketInspector?.FillReceiveBuffer(buffer); + + var hasRemainingLength = buffer[1] != 0; + if (!hasRemainingLength) { - // The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length. - // So in all cases at least 2 bytes must be read for a complete MQTT packet. - var buffer = _fixedHeaderBuffer; - var totalBytesRead = 0; + return new ReadFixedHeaderResult + { + FixedHeader = new MqttFixedHeader(buffer[0], 0, totalBytesRead) + }; + } - while (totalBytesRead < buffer.Length) + var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false); + if (bodyLength == 0) + { + return new ReadFixedHeaderResult { - // Check two times for cancellation because the call to _ReadAsync_ might block for some time. - if (cancellationToken.IsCancellationRequested) - { - return ReadFixedHeaderResult.Canceled; - } + IsConnectionClosed = true + }; + } - int bytesRead; - try - { - bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - return ReadFixedHeaderResult.Canceled; - } - catch (SocketException) - { - return ReadFixedHeaderResult.ConnectionClosed; - } + totalBytesRead += bodyLength; + return new ReadFixedHeaderResult + { + FixedHeader = new MqttFixedHeader(buffer[0], bodyLength, totalBytesRead) + }; + } - if (cancellationToken.IsCancellationRequested) - { - return ReadFixedHeaderResult.Canceled; - } + async Task ReceiveAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return ReceivedMqttPacket.Empty; + } - if (bytesRead == 0) - { - return ReadFixedHeaderResult.ConnectionClosed; - } + var readFixedHeaderResult = await ReadFixedHeaderAsync(cancellationToken).ConfigureAwait(false); - totalBytesRead += bytesRead; - } + if (cancellationToken.IsCancellationRequested) + { + return ReceivedMqttPacket.Empty; + } - PacketInspector?.FillReceiveBuffer(buffer); + if (readFixedHeaderResult.IsConnectionClosed) + { + return ReceivedMqttPacket.Empty; + } - var hasRemainingLength = buffer[1] != 0; - if (!hasRemainingLength) - { - return new ReadFixedHeaderResult - { - FixedHeader = new MqttFixedHeader(buffer[0], 0, totalBytesRead) - }; - } + var fixedHeader = readFixedHeaderResult.FixedHeader; + if (fixedHeader.RemainingLength == 0) + { + return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2); + } - var bodyLength = await ReadBodyLengthAsync(buffer[1], cancellationToken).ConfigureAwait(false); - if (bodyLength == 0) - { - return new ReadFixedHeaderResult - { - IsConnectionClosed = true - }; - } + var bodyLength = fixedHeader.RemainingLength; + var body = new byte[bodyLength]; - totalBytesRead += bodyLength; - return new ReadFixedHeaderResult - { - FixedHeader = new MqttFixedHeader(buffer[0], bodyLength, totalBytesRead) - }; - } + var bodyOffset = 0; + var chunkSize = Math.Min(ReadBufferSize, bodyLength); - async Task ReceiveAsync(CancellationToken cancellationToken) + do { - if (cancellationToken.IsCancellationRequested) + var bytesLeft = body.Length - bodyOffset; + if (chunkSize > bytesLeft) { - return ReceivedMqttPacket.Empty; + chunkSize = bytesLeft; } - var readFixedHeaderResult = await ReadFixedHeaderAsync(cancellationToken).ConfigureAwait(false); + var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { return ReceivedMqttPacket.Empty; } - if (readFixedHeaderResult.IsConnectionClosed) + if (readBytes == 0) { return ReceivedMqttPacket.Empty; } - var fixedHeader = readFixedHeaderResult.FixedHeader; - if (fixedHeader.RemainingLength == 0) - { - return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2); - } - - var bodyLength = fixedHeader.RemainingLength; - var body = new byte[bodyLength]; - - var bodyOffset = 0; - var chunkSize = Math.Min(ReadBufferSize, bodyLength); - - do - { - var bytesLeft = body.Length - bodyOffset; - if (chunkSize > bytesLeft) - { - chunkSize = bytesLeft; - } - - var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false); - - if (cancellationToken.IsCancellationRequested) - { - return ReceivedMqttPacket.Empty; - } + bodyOffset += readBytes; + } while (bodyOffset < bodyLength); - if (readBytes == 0) - { - return ReceivedMqttPacket.Empty; - } + PacketInspector?.FillReceiveBuffer(body); - bodyOffset += readBytes; - } while (bodyOffset < bodyLength); + var bodySegment = new ArraySegment(body, 0, bodyLength); + return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength); + } - PacketInspector?.FillReceiveBuffer(body); + static bool WrapAndThrowException(Exception exception) + { + if (exception is OperationCanceledException || exception is MqttCommunicationTimedOutException || exception is MqttCommunicationException || + exception is MqttProtocolViolationException) + { + return false; + } - var bodySegment = new ArraySegment(body, 0, bodyLength); - return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength); + if (exception is IOException && exception.InnerException is SocketException innerException) + { + exception = innerException; } - static bool WrapAndThrowException(Exception exception) + if (exception is SocketException socketException) { - if (exception is OperationCanceledException || exception is MqttCommunicationTimedOutException || exception is MqttCommunicationException || - exception is MqttProtocolViolationException) + if (socketException.SocketErrorCode == SocketError.OperationAborted) { - return false; + throw new OperationCanceledException(); } - if (exception is IOException && exception.InnerException is SocketException innerException) + if (socketException.SocketErrorCode == SocketError.ConnectionAborted) { - exception = innerException; + throw new MqttCommunicationException(socketException); } + } - if (exception is SocketException socketException) + if (exception is COMException comException) + { + if ((uint)comException.HResult == ErrorOperationAborted) { - if (socketException.SocketErrorCode == SocketError.OperationAborted) - { - throw new OperationCanceledException(); - } - - if (socketException.SocketErrorCode == SocketError.ConnectionAborted) - { - throw new MqttCommunicationException(socketException); - } + throw new OperationCanceledException(); } + } - if (exception is COMException comException) - { - if ((uint)comException.HResult == ErrorOperationAborted) - { - throw new OperationCanceledException(); - } - } + throw new MqttCommunicationException(exception); + } - throw new MqttCommunicationException(exception); - } + struct Statistics + { + public long _bytesReceived; + public long _bytesSent; - struct Statistics + public void Reset() { - public long _bytesReceived; - public long _bytesSent; - - public void Reset() - { - Volatile.Write(ref _bytesReceived, 0); - Volatile.Write(ref _bytesSent, 0); - } + Volatile.Write(ref _bytesReceived, 0); + Volatile.Write(ref _bytesSent, 0); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs index ea3cb470b..3700fdca4 100644 --- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs +++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs @@ -6,18 +6,16 @@ using MQTTnet.Client; using MQTTnet.Exceptions; -namespace MQTTnet.Adapter +namespace MQTTnet.Adapter; + +public sealed class MqttConnectingFailedException : MqttCommunicationException { - public sealed class MqttConnectingFailedException : MqttCommunicationException + public MqttConnectingFailedException(string message, Exception innerException, MqttClientConnectResult connectResult) : base(message, innerException) { - public MqttConnectingFailedException(string message, Exception innerException, MqttClientConnectResult connectResult) - : base(message, innerException) - { - Result = connectResult; - } + Result = connectResult; + } - public MqttClientConnectResult Result { get; } + public MqttClientConnectResult Result { get; } - public MqttClientConnectResultCode ResultCode => Result?.ResultCode ?? MqttClientConnectResultCode.UnspecifiedError; - } -} + public MqttClientConnectResultCode ResultCode => Result?.ResultCode ?? MqttClientConnectResultCode.UnspecifiedError; +} \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/MqttPacketInspector.cs b/Source/MQTTnet/Adapter/MqttPacketInspector.cs index 4f0c089e9..88cb1a305 100644 --- a/Source/MQTTnet/Adapter/MqttPacketInspector.cs +++ b/Source/MQTTnet/Adapter/MqttPacketInspector.cs @@ -9,91 +9,90 @@ using MQTTnet.Formatter; using MQTTnet.Internal; -namespace MQTTnet.Adapter +namespace MQTTnet.Adapter; + +public sealed class MqttPacketInspector { - public sealed class MqttPacketInspector - { - readonly AsyncEvent _asyncEvent; - readonly MqttNetSourceLogger _logger; + readonly AsyncEvent _asyncEvent; + readonly MqttNetSourceLogger _logger; - MemoryStream _receivedPacketBuffer; + MemoryStream _receivedPacketBuffer; + + public MqttPacketInspector(AsyncEvent asyncEvent, IMqttNetLogger logger) + { + _asyncEvent = asyncEvent ?? throw new ArgumentNullException(nameof(asyncEvent)); - public MqttPacketInspector(AsyncEvent asyncEvent, IMqttNetLogger logger) + if (logger == null) { - _asyncEvent = asyncEvent ?? throw new ArgumentNullException(nameof(asyncEvent)); + throw new ArgumentNullException(nameof(logger)); + } - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } + _logger = logger.WithSource(nameof(MqttPacketInspector)); + } - _logger = logger.WithSource(nameof(MqttPacketInspector)); + public void BeginReceivePacket() + { + if (!_asyncEvent.HasHandlers) + { + return; } - public void BeginReceivePacket() + if (_receivedPacketBuffer == null) { - if (!_asyncEvent.HasHandlers) - { - return; - } + _receivedPacketBuffer = new MemoryStream(); + } - if (_receivedPacketBuffer == null) - { - _receivedPacketBuffer = new MemoryStream(); - } + _receivedPacketBuffer?.SetLength(0); + } - _receivedPacketBuffer?.SetLength(0); + public Task BeginSendPacket(MqttPacketBuffer buffer) + { + if (!_asyncEvent.HasHandlers) + { + return CompletedTask.Instance; } - public Task BeginSendPacket(MqttPacketBuffer buffer) - { - if (!_asyncEvent.HasHandlers) - { - return CompletedTask.Instance; - } + // Create a copy of the actual packet so that the inspector gets no access + // to the internal buffers. This is waste of memory but this feature is only + // intended for debugging etc. so that this is OK. + var bufferCopy = buffer.ToArray(); - // Create a copy of the actual packet so that the inspector gets no access - // to the internal buffers. This is waste of memory but this feature is only - // intended for debugging etc. so that this is OK. - var bufferCopy = buffer.ToArray(); + return InspectPacket(bufferCopy, MqttPacketFlowDirection.Outbound); + } - return InspectPacket(bufferCopy, MqttPacketFlowDirection.Outbound); + public Task EndReceivePacket() + { + if (!_asyncEvent.HasHandlers) + { + return CompletedTask.Instance; } - public Task EndReceivePacket() - { - if (!_asyncEvent.HasHandlers) - { - return CompletedTask.Instance; - } + var buffer = _receivedPacketBuffer.ToArray(); + _receivedPacketBuffer.SetLength(0); - var buffer = _receivedPacketBuffer.ToArray(); - _receivedPacketBuffer.SetLength(0); + return InspectPacket(buffer, MqttPacketFlowDirection.Inbound); + } - return InspectPacket(buffer, MqttPacketFlowDirection.Inbound); + public void FillReceiveBuffer(byte[] buffer) + { + if (!_asyncEvent.HasHandlers) + { + return; } - public void FillReceiveBuffer(byte[] buffer) - { - if (!_asyncEvent.HasHandlers) - { - return; - } + _receivedPacketBuffer?.Write(buffer, 0, buffer.Length); + } - _receivedPacketBuffer?.Write(buffer, 0, buffer.Length); + async Task InspectPacket(byte[] buffer, MqttPacketFlowDirection direction) + { + try + { + var eventArgs = new InspectMqttPacketEventArgs(direction, buffer); + await _asyncEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } - - async Task InspectPacket(byte[] buffer, MqttPacketFlowDirection direction) + catch (Exception exception) { - try - { - var eventArgs = new InspectMqttPacketEventArgs(direction, buffer); - await _asyncEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - } - catch (Exception exception) - { - _logger.Error(exception, "Error while inspecting packet."); - } + _logger.Error(exception, "Error while inspecting packet."); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs index 74815cb2b..f290e5656 100644 --- a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs +++ b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs @@ -4,23 +4,22 @@ using System; -namespace MQTTnet.Adapter +namespace MQTTnet.Adapter; + +public readonly struct ReceivedMqttPacket { - public readonly struct ReceivedMqttPacket + public static readonly ReceivedMqttPacket Empty = new(); + + public ReceivedMqttPacket(byte fixedHeader, ArraySegment body, int totalLength) { - public static readonly ReceivedMqttPacket Empty = new ReceivedMqttPacket(); - - public ReceivedMqttPacket(byte fixedHeader, ArraySegment body, int totalLength) - { - FixedHeader = fixedHeader; - Body = body; - TotalLength = totalLength; - } + FixedHeader = fixedHeader; + Body = body; + TotalLength = totalLength; + } - public byte FixedHeader { get; } + public byte FixedHeader { get; } - public ArraySegment Body { get; } + public ArraySegment Body { get; } - public int TotalLength { get; } - } -} + public int TotalLength { get; } +} \ No newline at end of file diff --git a/Source/MQTTnet/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs index 9705c330f..2f0a6be51 100644 --- a/Source/MQTTnet/Channel/IMqttChannel.cs +++ b/Source/MQTTnet/Channel/IMqttChannel.cs @@ -7,22 +7,20 @@ using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.Channel +namespace MQTTnet.Channel; + +public interface IMqttChannel : IDisposable { - public interface IMqttChannel : IDisposable - { - string Endpoint { get; } - - bool IsSecureConnection { get; } - - X509Certificate2 ClientCertificate { get; } - - Task ConnectAsync(CancellationToken cancellationToken); - - Task DisconnectAsync(CancellationToken cancellationToken); - - Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); - - Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken); - } -} + X509Certificate2 ClientCertificate { get; } + string Endpoint { get; } + + bool IsSecureConnection { get; } + + Task ConnectAsync(CancellationToken cancellationToken); + + Task DisconnectAsync(CancellationToken cancellationToken); + + Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); + + Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs index 831d14cba..8ae724e5c 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs @@ -6,115 +6,116 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientConnectResult { - public sealed class MqttClientConnectResult - { - /// - /// Gets the result code. - /// MQTTv5 only. - /// - public MqttClientConnectResultCode ResultCode { get; internal set; } - - /// - /// Gets a value indicating whether a session was already available or not. - /// MQTTv5 only. - /// - public bool IsSessionPresent { get; internal set; } - - /// - /// Gets a value indicating whether wildcards can be used in subscriptions at the current server. - /// MQTTv5 only. - /// - public bool WildcardSubscriptionAvailable { get; internal set; } - - /// - /// Gets whether the server supports retained messages. - /// MQTTv5 only. - /// - public bool RetainAvailable { get; internal set; } - - /// - /// Gets the client identifier which was chosen by the server. - /// MQTTv5 only. - /// - public string AssignedClientIdentifier { get; internal set; } - - /// - /// Gets the authentication method. - /// MQTTv5 only. - /// - public string AuthenticationMethod { get; internal set; } - - /// - /// Gets the authentication data. - /// MQTTv5 only. - /// - public byte[] AuthenticationData { get; internal set; } - - public uint? MaximumPacketSize { get; internal set; } - - /// - /// Gets the reason string. - /// MQTTv5 only. - /// - public string ReasonString { get; internal set; } - - public ushort? ReceiveMaximum { get; internal set; } - - /// - /// Gets the maximum QoS which is supported by the server. - /// MQTTv5 only. - /// - public MqttQualityOfServiceLevel MaximumQoS { get; internal set; } - - /// - /// Gets the response information. - /// MQTTv5 only. - /// - public string ResponseInformation { get; internal set; } - - /// - /// Gets the maximum value for a topic alias. 0 means not supported. - /// MQTTv5 only. - /// - public ushort TopicAliasMaximum { get; internal set; } - - /// - /// Gets an alternate server which should be used instead of the current one. - /// MQTTv5 only. - /// - public string ServerReference { get; internal set; } - - /// - /// MQTTv5 only. - /// Gets the keep alive interval which was chosen by the server instead of the - /// 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 uint? SessionExpiryInterval { get; internal set; } - - /// - /// Gets a value indicating whether the subscription identifiers are available or not. - /// MQTTv5 only. - /// - public bool SubscriptionIdentifiersAvailable { get; internal set; } - - /// - /// Gets a value indicating whether the shared subscriptions are available or not. - /// MQTTv5 only. - /// - public bool SharedSubscriptionAvailable { get; internal set; } - - /// - /// Gets 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. - /// MQTTv5 only. - /// - public List UserProperties { get; internal set; } - } -} + /// + /// Gets the client identifier which was chosen by the server. + /// MQTTv5 only. + /// + public string AssignedClientIdentifier { get; internal set; } + + /// + /// Gets the authentication data. + /// MQTTv5 only. + /// + public byte[] AuthenticationData { get; internal set; } + + /// + /// Gets the authentication method. + /// MQTTv5 only. + /// + public string AuthenticationMethod { get; internal set; } + + /// + /// Gets a value indicating whether a session was already available or not. + /// MQTTv5 only. + /// + public bool IsSessionPresent { get; internal set; } + + public uint? MaximumPacketSize { get; internal set; } + + /// + /// Gets the maximum QoS which is supported by the server. + /// MQTTv5 only. + /// + public MqttQualityOfServiceLevel MaximumQoS { get; internal set; } + + /// + /// Gets the reason string. + /// MQTTv5 only. + /// + public string ReasonString { get; internal set; } + + public ushort? ReceiveMaximum { get; internal set; } + + /// + /// Gets the response information. + /// MQTTv5 only. + /// + public string ResponseInformation { get; internal set; } + + /// + /// Gets the result code. + /// MQTTv5 only. + /// + public MqttClientConnectResultCode ResultCode { get; internal set; } + + /// + /// Gets whether the server supports retained messages. + /// MQTTv5 only. + /// + public bool RetainAvailable { get; internal set; } + + /// + /// MQTTv5 only. + /// Gets the keep alive interval which was chosen by the server instead of the + /// 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; } + + /// + /// Gets an alternate server which should be used instead of the current one. + /// MQTTv5 only. + /// + public string ServerReference { get; internal set; } + + public uint? SessionExpiryInterval { get; internal set; } + + /// + /// Gets a value indicating whether the shared subscriptions are available or not. + /// MQTTv5 only. + /// + public bool SharedSubscriptionAvailable { get; internal set; } + + /// + /// Gets a value indicating whether the subscription identifiers are available or not. + /// MQTTv5 only. + /// + public bool SubscriptionIdentifiersAvailable { get; internal set; } + + /// + /// Gets the maximum value for a topic alias. 0 means not supported. + /// MQTTv5 only. + /// + public ushort TopicAliasMaximum { get; internal set; } + + /// + /// Gets 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. + /// MQTTv5 only. + /// + public List UserProperties { get; internal set; } + + /// + /// Gets a value indicating whether wildcards can be used in subscriptions at the current server. + /// MQTTv5 only. + /// + public bool WildcardSubscriptionAvailable { get; internal set; } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs index a25aa07d4..8dc88456e 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.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.Client +namespace MQTTnet.Client; + +public enum MqttClientConnectResultCode { - public enum MqttClientConnectResultCode - { - 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/Client/Connecting/MqttClientConnectResultFactory.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultFactory.cs index f32a915ee..f5fc88b90 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectResultFactory.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultFactory.cs @@ -8,100 +8,108 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientConnectResultFactory { - public sealed class MqttClientConnectResultFactory + public MqttClientConnectResult Create(MqttConnAckPacket connAckPacket, MqttProtocolVersion protocolVersion) { - public MqttClientConnectResult Create(MqttConnAckPacket connAckPacket, MqttProtocolVersion protocolVersion) + if (connAckPacket == null) { - if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); + throw new ArgumentNullException(nameof(connAckPacket)); + } + + if (protocolVersion == MqttProtocolVersion.V500) + { + return CreateForMqtt500(connAckPacket); + } - if (protocolVersion == MqttProtocolVersion.V500) + return CreateForMqtt311(connAckPacket); + } + + static MqttClientConnectResultCode ConvertReturnCodeToResultCode(MqttConnectReturnCode connectReturnCode) + { + switch (connectReturnCode) + { + case MqttConnectReturnCode.ConnectionAccepted: { - return CreateForMqtt500(connAckPacket); + return MqttClientConnectResultCode.Success; } - return CreateForMqtt311(connAckPacket); - } - - static MqttClientConnectResult CreateForMqtt500(MqttConnAckPacket connAckPacket) - { - if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); + case MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion: + { + return MqttClientConnectResultCode.UnsupportedProtocolVersion; + } - return new MqttClientConnectResult + case MqttConnectReturnCode.ConnectionRefusedNotAuthorized: { - IsSessionPresent = connAckPacket.IsSessionPresent, - ResultCode = (MqttClientConnectResultCode) (int) connAckPacket.ReasonCode, - WildcardSubscriptionAvailable = connAckPacket.WildcardSubscriptionAvailable, - RetainAvailable = connAckPacket.RetainAvailable, - AssignedClientIdentifier = connAckPacket.AssignedClientIdentifier, - AuthenticationMethod = connAckPacket.AuthenticationMethod, - AuthenticationData = connAckPacket.AuthenticationData, - MaximumPacketSize = connAckPacket.MaximumPacketSize, - ReasonString = connAckPacket.ReasonString, - ReceiveMaximum = connAckPacket.ReceiveMaximum, - MaximumQoS = connAckPacket.MaximumQoS, - ResponseInformation = connAckPacket.ResponseInformation, - TopicAliasMaximum = connAckPacket.TopicAliasMaximum, - ServerReference = connAckPacket.ServerReference, - ServerKeepAlive = connAckPacket.ServerKeepAlive, - SessionExpiryInterval = connAckPacket.SessionExpiryInterval, - SubscriptionIdentifiersAvailable = connAckPacket.SubscriptionIdentifiersAvailable, - SharedSubscriptionAvailable = connAckPacket.SharedSubscriptionAvailable, - UserProperties = connAckPacket.UserProperties - }; - } - - static MqttClientConnectResult CreateForMqtt311(MqttConnAckPacket connAckPacket) - { - if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); + return MqttClientConnectResultCode.NotAuthorized; + } - return new MqttClientConnectResult + case MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword: { - RetainAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature. - WildcardSubscriptionAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature. - IsSessionPresent = connAckPacket.IsSessionPresent, - ResultCode = ConvertReturnCodeToResultCode(connAckPacket.ReturnCode) - }; - } + return MqttClientConnectResultCode.BadUserNameOrPassword; + } - static MqttClientConnectResultCode ConvertReturnCodeToResultCode(MqttConnectReturnCode connectReturnCode) - { - switch (connectReturnCode) + case MqttConnectReturnCode.ConnectionRefusedIdentifierRejected: { - 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."); + return MqttClientConnectResultCode.ClientIdentifierNotValid; } + + case MqttConnectReturnCode.ConnectionRefusedServerUnavailable: + { + return MqttClientConnectResultCode.ServerUnavailable; + } + + default: + throw new MqttProtocolViolationException("Received unexpected return code."); + } + } + + static MqttClientConnectResult CreateForMqtt311(MqttConnAckPacket connAckPacket) + { + if (connAckPacket == null) + { + throw new ArgumentNullException(nameof(connAckPacket)); } + + return new MqttClientConnectResult + { + RetainAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature. + WildcardSubscriptionAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature. + IsSessionPresent = connAckPacket.IsSessionPresent, + ResultCode = ConvertReturnCodeToResultCode(connAckPacket.ReturnCode) + }; + } + + static MqttClientConnectResult CreateForMqtt500(MqttConnAckPacket connAckPacket) + { + if (connAckPacket == null) + { + throw new ArgumentNullException(nameof(connAckPacket)); + } + + return new MqttClientConnectResult + { + IsSessionPresent = connAckPacket.IsSessionPresent, + ResultCode = (MqttClientConnectResultCode)(int)connAckPacket.ReasonCode, + WildcardSubscriptionAvailable = connAckPacket.WildcardSubscriptionAvailable, + RetainAvailable = connAckPacket.RetainAvailable, + AssignedClientIdentifier = connAckPacket.AssignedClientIdentifier, + AuthenticationMethod = connAckPacket.AuthenticationMethod, + AuthenticationData = connAckPacket.AuthenticationData, + MaximumPacketSize = connAckPacket.MaximumPacketSize, + ReasonString = connAckPacket.ReasonString, + ReceiveMaximum = connAckPacket.ReceiveMaximum, + MaximumQoS = connAckPacket.MaximumQoS, + ResponseInformation = connAckPacket.ResponseInformation, + TopicAliasMaximum = connAckPacket.TopicAliasMaximum, + ServerReference = connAckPacket.ServerReference, + ServerKeepAlive = connAckPacket.ServerKeepAlive, + SessionExpiryInterval = connAckPacket.SessionExpiryInterval, + SubscriptionIdentifiersAvailable = connAckPacket.SubscriptionIdentifiersAvailable, + SharedSubscriptionAvailable = connAckPacket.SharedSubscriptionAvailable, + UserProperties = connAckPacket.UserProperties + }; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs index 2ea089789..2b6421753 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs @@ -4,19 +4,18 @@ using System; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientConnectedEventArgs : EventArgs { - public sealed class MqttClientConnectedEventArgs : EventArgs + public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult) { - public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult) - { - ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult)); - } - - /// - /// Gets the authentication result. - /// MQTT 5.0.0+ feature. - /// - public MqttClientConnectResult ConnectResult { get; } + ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult)); } + + /// + /// Gets the authentication result. + /// MQTT 5.0.0+ feature. + /// + public MqttClientConnectResult ConnectResult { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectingEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectingEventArgs.cs index a4b87bcc0..f8dc27c92 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectingEventArgs.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectingEventArgs.cs @@ -4,15 +4,14 @@ using System; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientConnectingEventArgs : EventArgs { - public sealed class MqttClientConnectingEventArgs : EventArgs + public MqttClientConnectingEventArgs(MqttClientOptions clientOptions) { - public MqttClientConnectingEventArgs(MqttClientOptions clientOptions) - { - ClientOptions = clientOptions; - } - - public MqttClientOptions ClientOptions { get; } + ClientOptions = clientOptions; } + + public MqttClientOptions ClientOptions { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs index 7f57ca2a7..48b1f52fc 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs @@ -5,32 +5,31 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientDisconnectOptions { - public sealed class MqttClientDisconnectOptions - { - /// - /// Gets or sets the reason code. - /// MQTT 5.0.0+ feature. - /// - public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection; + /// + /// Gets or sets the reason code. + /// MQTT 5.0.0+ feature. + /// + public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection; - /// - /// Gets or sets the reason string. - /// MQTT 5.0.0+ feature. - /// - public string ReasonString { get; set; } + /// + /// Gets or sets the reason string. + /// MQTT 5.0.0+ feature. + /// + public string ReasonString { get; set; } - /// - /// Gets or sets the session expiry interval. - /// MQTT 5.0.0+ feature. - /// - public uint SessionExpiryInterval { get; set; } + /// + /// Gets or sets the session expiry interval. + /// MQTT 5.0.0+ feature. + /// + public uint SessionExpiryInterval { 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/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs index 3c4023097..a83cc9633 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs @@ -5,59 +5,58 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientDisconnectOptionsBuilder { - public sealed class MqttClientDisconnectOptionsBuilder - { - MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection; - string _reasonString; - uint _sessionExpiryInterval; - List _userProperties; + MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection; + string _reasonString; + uint _sessionExpiryInterval; + List _userProperties; - public MqttClientDisconnectOptions Build() + public MqttClientDisconnectOptions Build() + { + return new MqttClientDisconnectOptions { - return new MqttClientDisconnectOptions - { - Reason = _reason, - ReasonString = _reasonString, - UserProperties = _userProperties, - SessionExpiryInterval = _sessionExpiryInterval - }; - } + Reason = _reason, + ReasonString = _reasonString, + UserProperties = _userProperties, + SessionExpiryInterval = _sessionExpiryInterval + }; + } - public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value) - { - _reason = value; - return this; - } + public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value) + { + _reason = value; + return this; + } - public MqttClientDisconnectOptionsBuilder WithReasonString(string value) - { - _reasonString = value; - return this; - } + public MqttClientDisconnectOptionsBuilder WithReasonString(string value) + { + _reasonString = value; + return this; + } - public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value) - { - _sessionExpiryInterval = value; - return this; - } + public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value) + { + _sessionExpiryInterval = value; + return this; + } - public MqttClientDisconnectOptionsBuilder WithUserProperties(List userProperties) - { - _userProperties = userProperties; - return this; - } + public MqttClientDisconnectOptionsBuilder WithUserProperties(List userProperties) + { + _userProperties = userProperties; + return this; + } - public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value) + public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value) + { + if (_userProperties == null) { - if (_userProperties == null) - { - _userProperties = new List(); - } - - _userProperties.Add(new MqttUserProperty(name, value)); - return this; + _userProperties = new List(); } + + _userProperties.Add(new MqttUserProperty(name, value)); + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs index c51ec9959..9f47f380d 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs @@ -2,26 +2,25 @@ // 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.Client +namespace MQTTnet.Client; + +/// +/// This enum only contains values which are valid when a client sends the reason to the server. +/// +public enum MqttClientDisconnectOptionsReason { - /// - /// This enum only contains values which are valid when a client sends the reason to the server. - /// - public enum MqttClientDisconnectOptionsReason - { - NormalDisconnection = 0, - DisconnectWithWillMessage = 4, - UnspecifiedError = 128, - MalformedPacket = 129, - ProtocolError = 130, - ImplementationSpecificError = 131, - TopicNameInvalid = 144, - ReceiveMaximumExceeded = 147, - TopicAliasInvalid = 148, - PacketTooLarge = 149, - MessageRateTooHigh = 150, - QuotaExceeded = 151, - AdministrativeAction = 152, - PayloadFormatInvalid = 153 - } + NormalDisconnection = 0, + DisconnectWithWillMessage = 4, + UnspecifiedError = 128, + MalformedPacket = 129, + ProtocolError = 130, + ImplementationSpecificError = 131, + TopicNameInvalid = 144, + ReceiveMaximumExceeded = 147, + TopicAliasInvalid = 148, + PacketTooLarge = 149, + MessageRateTooHigh = 150, + QuotaExceeded = 151, + AdministrativeAction = 152, + PayloadFormatInvalid = 153 } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs index 0a0604831..ec5624813 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs @@ -6,37 +6,36 @@ using System.Linq; using MQTTnet.Formatter; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public static class MqttClientDisconnectOptionsValidator { - public static class MqttClientDisconnectOptionsValidator + public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion) { - public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion) + if (options == null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (protocolVersion == MqttProtocolVersion.V500) - { - // Everything is supported. - return; - } + throw new ArgumentNullException(nameof(options)); + } - if (options.ReasonString?.Any() == true) - { - Throw(nameof(options.ReasonString)); - } + if (protocolVersion == MqttProtocolVersion.V500) + { + // Everything is supported. + return; + } - if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection) - { - Throw(nameof(options.Reason)); - } + if (options.ReasonString?.Any() == true) + { + Throw(nameof(options.ReasonString)); } - static void Throw(string featureName) + if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection) { - throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0."); + Throw(nameof(options.Reason)); } } + + 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/Client/Disconnecting/MqttClientDisconnectReason.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs index a45113245..47eb0d66a 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs @@ -2,39 +2,38 @@ // 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.Client +namespace MQTTnet.Client; + +public enum MqttClientDisconnectReason { - public enum MqttClientDisconnectReason - { - NormalDisconnection = 0, - DisconnectWithWillMessage = 4, - UnspecifiedError = 128, - MalformedPacket = 129, - ProtocolError = 130, - ImplementationSpecificError = 131, - NotAuthorized = 135, - ServerBusy = 137, - ServerShuttingDown = 139, - BadAuthenticationMethod = 140, - 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, + BadAuthenticationMethod = 140, + 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/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs index 0a24e44f1..dcd745539 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs @@ -6,44 +6,43 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientDisconnectedEventArgs : EventArgs { - public sealed class MqttClientDisconnectedEventArgs : EventArgs + public MqttClientDisconnectedEventArgs( + bool clientWasConnected, + MqttClientConnectResult connectResult, + MqttClientDisconnectReason reason, + string reasonString, + List userProperties, + Exception exception) { - public MqttClientDisconnectedEventArgs( - bool clientWasConnected, - MqttClientConnectResult connectResult, - MqttClientDisconnectReason reason, - string reasonString, - List userProperties, - Exception exception) - { - ClientWasConnected = clientWasConnected; - ConnectResult = connectResult; - Exception = exception; - Reason = reason; - ReasonString = reasonString; - UserProperties = userProperties; - } - - public bool ClientWasConnected { get; } - - /// - /// Gets the authentication result. - /// MQTT 5.0.0+ feature. - /// - public MqttClientConnectResult ConnectResult { get; } - - public Exception Exception { get; } - - /// - /// Gets or sets the reason. - /// MQTT 5.0.0+ feature. - /// - public MqttClientDisconnectReason Reason { get; } - - public string ReasonString { get; } - - public List UserProperties { get; } + ClientWasConnected = clientWasConnected; + ConnectResult = connectResult; + Exception = exception; + Reason = reason; + ReasonString = reasonString; + UserProperties = userProperties; } + + public bool ClientWasConnected { get; } + + /// + /// Gets the authentication result. + /// MQTT 5.0.0+ feature. + /// + public MqttClientConnectResult ConnectResult { get; } + + public Exception Exception { get; } + + /// + /// Gets or sets the reason. + /// MQTT 5.0.0+ feature. + /// + public MqttClientDisconnectReason Reason { get; } + + public string ReasonString { get; } + + public List UserProperties { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Exceptions/MqttClientDisconnectedException.cs b/Source/MQTTnet/Client/Exceptions/MqttClientDisconnectedException.cs index ce548428b..4b0eb1f0f 100644 --- a/Source/MQTTnet/Client/Exceptions/MqttClientDisconnectedException.cs +++ b/Source/MQTTnet/Client/Exceptions/MqttClientDisconnectedException.cs @@ -5,12 +5,11 @@ using System; using MQTTnet.Exceptions; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientDisconnectedException : MqttCommunicationException { - public sealed class MqttClientDisconnectedException : MqttCommunicationException + public MqttClientDisconnectedException(Exception innerException) : base("The MQTT client is disconnected.", innerException) { - public MqttClientDisconnectedException(Exception innerException) : base("The MQTT client is disconnected.", innerException) - { - } } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs b/Source/MQTTnet/Client/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs index 57cfeafee..8c3520f76 100644 --- a/Source/MQTTnet/Client/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs +++ b/Source/MQTTnet/Client/Exceptions/MqttClientUnexpectedDisconnectReceivedException.cs @@ -8,29 +8,28 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException { - public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException + public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket, Exception innerExcpetion = null) : base( + $"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.", + innerExcpetion) { - 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; - } + ReasonCode = disconnectPacket.ReasonCode; + SessionExpiryInterval = disconnectPacket.SessionExpiryInterval; + ReasonString = disconnectPacket.ReasonString; + ServerReference = disconnectPacket.ServerReference; + UserProperties = disconnectPacket.UserProperties; + } - public MqttDisconnectReasonCode? ReasonCode { get; } + public MqttDisconnectReasonCode? ReasonCode { get; } - public string ReasonString { get; } + public string ReasonString { get; } - public string ServerReference { get; } + public string ServerReference { get; } - public uint? SessionExpiryInterval { get; } + public uint? SessionExpiryInterval { get; } - public List UserProperties { get; } - } + public List UserProperties { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/ExtendedAuthenticationExchange/IMqttExtendedAuthenticationExchangeHandler.cs b/Source/MQTTnet/Client/ExtendedAuthenticationExchange/IMqttExtendedAuthenticationExchangeHandler.cs index 8b66334a0..4f73df01f 100644 --- a/Source/MQTTnet/Client/ExtendedAuthenticationExchange/IMqttExtendedAuthenticationExchangeHandler.cs +++ b/Source/MQTTnet/Client/ExtendedAuthenticationExchange/IMqttExtendedAuthenticationExchangeHandler.cs @@ -4,10 +4,9 @@ using System.Threading.Tasks; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public interface IMqttExtendedAuthenticationExchangeHandler { - public interface IMqttExtendedAuthenticationExchangeHandler - { - Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context); - } -} + Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context); +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeContext.cs b/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeContext.cs index c406b5ecd..0c91e0e7d 100644 --- a/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeContext.cs +++ b/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeContext.cs @@ -7,56 +7,60 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public class MqttExtendedAuthenticationExchangeContext { - public class MqttExtendedAuthenticationExchangeContext + public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client) { - public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client) + if (authPacket == null) { - if (authPacket == null) throw new ArgumentNullException(nameof(authPacket)); - - ReasonCode = authPacket.ReasonCode; - ReasonString = authPacket.ReasonString; - AuthenticationMethod = authPacket.AuthenticationMethod; - AuthenticationData = authPacket.AuthenticationData; - UserProperties = authPacket.UserProperties; - - Client = client ?? throw new ArgumentNullException(nameof(client)); + throw new ArgumentNullException(nameof(authPacket)); } - /// - /// Gets the reason code. - /// Hint: MQTT 5 feature only. - /// - public MqttAuthenticateReasonCode ReasonCode { get; } - - /// - /// Gets the reason string. - /// Hint: MQTT 5 feature only. - /// - public string ReasonString { get; } - - /// - /// Gets the authentication method. - /// Hint: MQTT 5 feature only. - /// - public string AuthenticationMethod { get; } - - /// - /// Gets the authentication data. - /// Hint: MQTT 5 feature only. - /// - public byte[] AuthenticationData { get; } - - /// - /// Gets 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; } - - public MqttClient Client { get; } + ReasonCode = authPacket.ReasonCode; + ReasonString = authPacket.ReasonString; + AuthenticationMethod = authPacket.AuthenticationMethod; + AuthenticationData = authPacket.AuthenticationData; + UserProperties = authPacket.UserProperties; + + Client = client ?? throw new ArgumentNullException(nameof(client)); } -} + + /// + /// Gets the authentication data. + /// Hint: MQTT 5 feature only. + /// + public byte[] AuthenticationData { get; } + + /// + /// Gets the authentication method. + /// Hint: MQTT 5 feature only. + /// + public string AuthenticationMethod { get; } + + public MqttClient Client { get; } + + /// + /// Gets the reason code. + /// Hint: MQTT 5 feature only. + /// + public MqttAuthenticateReasonCode ReasonCode { get; } + + /// + /// Gets the reason string. + /// Hint: MQTT 5 feature only. + /// + public string ReasonString { get; } + + /// + /// Gets 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; } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeData.cs b/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeData.cs index 74f146482..12207d321 100644 --- a/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeData.cs +++ b/Source/MQTTnet/Client/ExtendedAuthenticationExchange/MqttExtendedAuthenticationExchangeData.cs @@ -6,37 +6,40 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public class MqttExtendedAuthenticationExchangeData { - public class MqttExtendedAuthenticationExchangeData - { - /// - /// Gets or sets the reason code. - /// Hint: MQTT 5 feature only. - /// - public MqttAuthenticateReasonCode ReasonCode { get; set; } + /// + /// Gets or sets the authentication data. + /// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol + /// steps. + /// The content of the authentication data is highly dependent on the specific implementation of the authentication + /// method. + /// Hint: MQTT 5 feature only. + /// + public byte[] AuthenticationData { get; set; } - /// - /// Gets or sets the reason string. - /// Hint: MQTT 5 feature only. - /// - public string ReasonString { get; set; } + /// + /// Gets or sets the reason code. + /// Hint: MQTT 5 feature only. + /// + public MqttAuthenticateReasonCode ReasonCode { get; set; } - /// - /// Gets or sets the authentication data. - /// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol steps. - /// The content of the authentication data is highly dependent on the specific implementation of the authentication method. - /// Hint: MQTT 5 feature only. - /// - public byte[] AuthenticationData { get; set; } + /// + /// Gets or sets the reason string. + /// Hint: MQTT 5 feature only. + /// + public string ReasonString { 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; } - } -} + /// + /// 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; } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs index 033672d5a..7cb9952c6 100644 --- a/Source/MQTTnet/Client/IMqttClient.cs +++ b/Source/MQTTnet/Client/IMqttClient.cs @@ -3,36 +3,35 @@ using System.Threading.Tasks; using MQTTnet.Diagnostics; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public interface IMqttClient : IDisposable { - public interface IMqttClient : IDisposable - { - event Func ApplicationMessageReceivedAsync; - - event Func ConnectedAsync; + event Func ApplicationMessageReceivedAsync; + + event Func ConnectedAsync; - event Func ConnectingAsync; + event Func ConnectingAsync; - event Func DisconnectedAsync; + event Func DisconnectedAsync; - event Func InspectPacketAsync; + event Func InspectPacketAsync; - bool IsConnected { get; } + bool IsConnected { get; } - MqttClientOptions Options { get; } + MqttClientOptions Options { get; } - Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default); + Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default); - Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default); + Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default); - Task PingAsync(CancellationToken cancellationToken = default); + Task PingAsync(CancellationToken cancellationToken = default); - Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default); + Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default); - Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default); + Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default); - Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default); + Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default); - Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default); - } + Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Internal/MqttClientEvents.cs b/Source/MQTTnet/Client/Internal/MqttClientEvents.cs index fe786e82c..6e6202328 100644 --- a/Source/MQTTnet/Client/Internal/MqttClientEvents.cs +++ b/Source/MQTTnet/Client/Internal/MqttClientEvents.cs @@ -5,14 +5,13 @@ using MQTTnet.Diagnostics; using MQTTnet.Internal; -namespace MQTTnet.Client.Internal +namespace MQTTnet.Client.Internal; + +public sealed class MqttClientEvents { - public sealed class MqttClientEvents - { - public AsyncEvent ApplicationMessageReceivedEvent { get; } = new AsyncEvent(); - public AsyncEvent ConnectedEvent { get; } = new AsyncEvent(); - public AsyncEvent ConnectingEvent { get; } = new AsyncEvent(); - public AsyncEvent DisconnectedEvent { get; } = new AsyncEvent(); - public AsyncEvent InspectPacketEvent { get; } = new AsyncEvent(); - } + public AsyncEvent ApplicationMessageReceivedEvent { get; } = new(); + public AsyncEvent ConnectedEvent { get; } = new(); + public AsyncEvent ConnectingEvent { get; } = new(); + public AsyncEvent DisconnectedEvent { get; } = new(); + public AsyncEvent InspectPacketEvent { get; } = new(); } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Internal/MqttClientResultFactory.cs b/Source/MQTTnet/Client/Internal/MqttClientResultFactory.cs index 6629657eb..b3c909417 100644 --- a/Source/MQTTnet/Client/Internal/MqttClientResultFactory.cs +++ b/Source/MQTTnet/Client/Internal/MqttClientResultFactory.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.Client.Internal +namespace MQTTnet.Client.Internal; + +public static class MqttClientResultFactory { - public static class MqttClientResultFactory - { - public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory(); - public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory(); - public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory(); - public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory(); - } + 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/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 4bf34c062..951e7e40f 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -16,1080 +16,1079 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClient : Disposable, IMqttClient { - public sealed class MqttClient : Disposable, IMqttClient - { - readonly IMqttClientAdapterFactory _adapterFactory; + readonly IMqttClientAdapterFactory _adapterFactory; - readonly object _disconnectLock = new object(); - readonly MqttClientEvents _events = new MqttClientEvents(); - readonly MqttNetSourceLogger _logger; + readonly object _disconnectLock = new(); + readonly MqttClientEvents _events = new(); + readonly MqttNetSourceLogger _logger; - readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); - readonly IMqttNetLogger _rootLogger; + readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new(); + readonly IMqttNetLogger _rootLogger; - IMqttChannelAdapter _adapter; + IMqttChannelAdapter _adapter; - bool _cleanDisconnectInitiated; - volatile int _connectionStatus; + bool _cleanDisconnectInitiated; + volatile int _connectionStatus; - // The value for this field can be set from two different enums. - // They contain the same values but the set is reduced in one case. - int _disconnectReason; - string _disconnectReasonString; - List _disconnectUserProperties; + // The value for this field can be set from two different enums. + // They contain the same values but the set is reduced in one case. + int _disconnectReason; + string _disconnectReasonString; + List _disconnectUserProperties; - Task _keepAlivePacketsSenderTask; - DateTime _lastPacketSentTimestamp; + Task _keepAlivePacketsSenderTask; + DateTime _lastPacketSentTimestamp; - CancellationTokenSource _mqttClientAlive; - MqttPacketDispatcher _packetDispatcher; - Task _packetReceiverTask; - AsyncQueue _publishPacketReceiverQueue; - Task _publishPacketReceiverTask; - MqttDisconnectPacket _unexpectedDisconnectPacket; + CancellationTokenSource _mqttClientAlive; + MqttPacketDispatcher _packetDispatcher; + Task _packetReceiverTask; + AsyncQueue _publishPacketReceiverQueue; + Task _publishPacketReceiverTask; + MqttDisconnectPacket _unexpectedDisconnectPacket; - public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) - { - _adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); - _rootLogger = logger ?? throw new ArgumentNullException(nameof(logger)); - _logger = logger.WithSource(nameof(MqttClient)); - } + public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) + { + _adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); + _rootLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger.WithSource(nameof(MqttClient)); + } - public event Func ApplicationMessageReceivedAsync - { - add => _events.ApplicationMessageReceivedEvent.AddHandler(value); - remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value); - } + public event Func ApplicationMessageReceivedAsync + { + add => _events.ApplicationMessageReceivedEvent.AddHandler(value); + remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value); + } - public event Func ConnectedAsync - { - add => _events.ConnectedEvent.AddHandler(value); - remove => _events.ConnectedEvent.RemoveHandler(value); - } + public event Func ConnectedAsync + { + add => _events.ConnectedEvent.AddHandler(value); + remove => _events.ConnectedEvent.RemoveHandler(value); + } - public event Func ConnectingAsync - { - add => _events.ConnectingEvent.AddHandler(value); - remove => _events.ConnectingEvent.RemoveHandler(value); - } + public event Func ConnectingAsync + { + add => _events.ConnectingEvent.AddHandler(value); + remove => _events.ConnectingEvent.RemoveHandler(value); + } - public event Func DisconnectedAsync - { - add => _events.DisconnectedEvent.AddHandler(value); - remove => _events.DisconnectedEvent.RemoveHandler(value); - } + public event Func DisconnectedAsync + { + add => _events.DisconnectedEvent.AddHandler(value); + remove => _events.DisconnectedEvent.RemoveHandler(value); + } - public event Func InspectPacketAsync - { - add => _events.InspectPacketEvent.AddHandler(value); - remove => _events.InspectPacketEvent.RemoveHandler(value); - } + public event Func InspectPacketAsync + { + add => _events.InspectPacketEvent.AddHandler(value); + remove => _events.InspectPacketEvent.RemoveHandler(value); + } - public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected; + public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected; - public MqttClientOptions Options { get; private set; } + public MqttClientOptions Options { get; private set; } + + public async Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default) + { + ThrowIfOptionsInvalid(options); + ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); + ThrowIfDisposed(); - public async Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default) + if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected) { - ThrowIfOptionsInvalid(options); - ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); - ThrowIfDisposed(); + throw new InvalidOperationException("Not allowed to connect while connect/disconnect is pending."); + } - if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected) - { - throw new InvalidOperationException("Not allowed to connect while connect/disconnect is pending."); - } + MqttClientConnectResult connectResult = null; - MqttClientConnectResult connectResult = null; + try + { + Options = options; - try + if (_events.ConnectingEvent.HasHandlers) { - Options = options; - - if (_events.ConnectingEvent.HasHandlers) - { - await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options)); - } + await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options)); + } - Cleanup(); + Cleanup(); - _packetIdentifierProvider.Reset(); - _packetDispatcher = new MqttPacketDispatcher(); + _packetIdentifierProvider.Reset(); + _packetDispatcher = new MqttPacketDispatcher(); - _mqttClientAlive = new CancellationTokenSource(); - var mqttClientAliveToken = _mqttClientAlive.Token; + _mqttClientAlive = new CancellationTokenSource(); + var mqttClientAliveToken = _mqttClientAlive.Token; - var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger); - _adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter."); + var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger); + _adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter."); - _unexpectedDisconnectPacket = null; + _unexpectedDisconnectPacket = null; - if (cancellationToken.CanBeCanceled) - { - connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false); - } - else + if (cancellationToken.CanBeCanceled) + { + connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false); + } + else + { + // 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)) { - // 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); - } + connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false); } + } - if (connectResult.ResultCode != MqttClientConnectResultCode.Success) - { - _logger.Warning("Connecting failed: {0}", connectResult.ResultCode); - await DisconnectInternal(null, null, connectResult).ConfigureAwait(false); - return connectResult; - } + if (connectResult.ResultCode != MqttClientConnectResultCode.Success) + { + _logger.Warning("Connecting failed: {0}", connectResult.ResultCode); + await DisconnectInternal(null, null, connectResult).ConfigureAwait(false); + return connectResult; + } - _lastPacketSentTimestamp = DateTime.UtcNow; + _lastPacketSentTimestamp = DateTime.UtcNow; - var keepAliveInterval = Options.KeepAlivePeriod; - if (connectResult.ServerKeepAlive > 0) - { - _logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server"); - keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive); - } + var keepAliveInterval = Options.KeepAlivePeriod; + if (connectResult.ServerKeepAlive > 0) + { + _logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server"); + keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive); + } - if (keepAliveInterval != TimeSpan.Zero) - { - _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(mqttClientAliveToken), mqttClientAliveToken); - } + if (keepAliveInterval != TimeSpan.Zero) + { + _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(mqttClientAliveToken), mqttClientAliveToken); + } - CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting); + CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting); - _logger.Info("Connected"); + _logger.Info("Connected"); - await OnConnected(connectResult).ConfigureAwait(false); + await OnConnected(connectResult).ConfigureAwait(false); - return connectResult; - } - catch (Exception exception) + return connectResult; + } + catch (Exception exception) + { + if (exception is MqttConnectingFailedException connectingFailedException) { - if (exception is MqttConnectingFailedException connectingFailedException) - { - connectResult = connectingFailedException.Result; - } + connectResult = connectingFailedException.Result; + } - _disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError; + _disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError; - _logger.Error(exception, "Error while connecting with server"); + _logger.Error(exception, "Error while connecting with server"); - await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false); + await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false); - throw; - } + throw; } + } - public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default) + public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default) + { + if (options is null) { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } + throw new ArgumentNullException(nameof(options)); + } - ThrowIfDisposed(); + ThrowIfDisposed(); - var clientWasConnected = IsConnected; + var clientWasConnected = IsConnected; - if (DisconnectIsPendingOrFinished()) - { - return; - } + if (DisconnectIsPendingOrFinished()) + { + return; + } - try + try + { + if (!clientWasConnected) { - if (!clientWasConnected) - { - ThrowNotConnected(); - } - - _disconnectReason = (int)options.Reason; - _cleanDisconnectInitiated = true; - - if (Options.ValidateFeatures) - { - MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion); - } + ThrowNotConnected(); + } - // Sending the DISCONNECT may fail due to connection issues. The resulting exception - // must be thrown to let the caller know that the disconnect was not clean. - var disconnectPacket = MqttPacketFactories.Disconnect.Create(options); + _disconnectReason = (int)options.Reason; + _cleanDisconnectInitiated = true; - if (cancellationToken.CanBeCanceled) - { - await Send(disconnectPacket, cancellationToken).ConfigureAwait(false); - } - else - { - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - await Send(disconnectPacket, timeout.Token).ConfigureAwait(false); - } - } - } - finally + if (Options.ValidateFeatures) { - await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false); + MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion); } - } - - public async Task PingAsync(CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - ThrowIfNotConnected(); + // Sending the DISCONNECT may fail due to connection issues. The resulting exception + // must be thrown to let the caller know that the disconnect was not clean. + var disconnectPacket = MqttDisconnectPacketFactory.Create(options); if (cancellationToken.CanBeCanceled) { - await Request(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false); + await Send(disconnectPacket, cancellationToken).ConfigureAwait(false); } else { using (var timeout = new CancellationTokenSource(Options.Timeout)) { - await Request(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false); + await Send(disconnectPacket, timeout.Token).ConfigureAwait(false); } } } - - public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default) + finally { - cancellationToken.ThrowIfCancellationRequested(); + await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false); + } + } - MqttTopicValidator.ThrowIfInvalid(applicationMessage); + public async Task PingAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - ThrowIfDisposed(); - ThrowIfNotConnected(); + ThrowIfDisposed(); + ThrowIfNotConnected(); - if (Options.ValidateFeatures) + if (cancellationToken.CanBeCanceled) + { + await Request(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false); + } + else + { + using (var timeout = new CancellationTokenSource(Options.Timeout)) { - MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion); + await Request(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false); } + } + } - var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage); + public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - switch (applicationMessage.QualityOfServiceLevel) - { - case MqttQualityOfServiceLevel.AtMostOnce: - { - return PublishAtMostOnce(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - return PublishAtLeastOnce(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - return PublishExactlyOnce(publishPacket, cancellationToken); - } - default: - { - throw new NotSupportedException(); - } - } + MqttTopicValidator.ThrowIfInvalid(applicationMessage); + + ThrowIfDisposed(); + ThrowIfNotConnected(); + + if (Options.ValidateFeatures) + { + MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion); } - public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default) + var publishPacket = MqttPublishPacketFactory.Create(applicationMessage); + + switch (applicationMessage.QualityOfServiceLevel) { - if (data == null) + case MqttQualityOfServiceLevel.AtMostOnce: + { + return PublishAtMostOnce(publishPacket, cancellationToken); + } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + return PublishAtLeastOnce(publishPacket, cancellationToken); + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + return PublishExactlyOnce(publishPacket, cancellationToken); + } + default: { - throw new ArgumentNullException(nameof(data)); + throw new NotSupportedException(); } + } + } + + public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } - ThrowIfDisposed(); - ThrowIfNotConnected(); + ThrowIfDisposed(); + ThrowIfNotConnected(); - var authPacket = new MqttAuthPacket - { - // This must always be equal to the value from the CONNECT packet. So we use it here to ensure that. - AuthenticationMethod = Options.AuthenticationMethod, - AuthenticationData = data.AuthenticationData, - ReasonString = data.ReasonString, - ReasonCode = data.ReasonCode, - UserProperties = data.UserProperties - }; - - return Send(authPacket, cancellationToken); + var authPacket = new MqttAuthPacket + { + // This must always be equal to the value from the CONNECT packet. So we use it here to ensure that. + AuthenticationMethod = Options.AuthenticationMethod, + AuthenticationData = data.AuthenticationData, + ReasonString = data.ReasonString, + ReasonCode = data.ReasonCode, + UserProperties = data.UserProperties + }; + + return Send(authPacket, cancellationToken); + } + + public async Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); } - public async Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default) + foreach (var topicFilter in options.TopicFilters) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic); + } - foreach (var topicFilter in options.TopicFilters) - { - MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic); - } + ThrowIfDisposed(); + ThrowIfNotConnected(); - ThrowIfDisposed(); - ThrowIfNotConnected(); + if (Options.ValidateFeatures) + { + MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion); + } - if (Options.ValidateFeatures) + var subscribePacket = MqttSubscribePacketFactory.Create(options); + subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + + MqttSubAckPacket subAckPacket; + if (cancellationToken.CanBeCanceled) + { + subAckPacket = await Request(subscribePacket, cancellationToken).ConfigureAwait(false); + } + else + { + using (var timeout = new CancellationTokenSource(Options.Timeout)) { - MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion); + subAckPacket = await Request(subscribePacket, timeout.Token).ConfigureAwait(false); } + } - var subscribePacket = MqttPacketFactories.Subscribe.Create(options); - subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket); + } - MqttSubAckPacket subAckPacket; - if (cancellationToken.CanBeCanceled) - { - subAckPacket = await Request(subscribePacket, cancellationToken).ConfigureAwait(false); - } - else - { - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - subAckPacket = await Request(subscribePacket, timeout.Token).ConfigureAwait(false); - } - } + public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } - return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket); + foreach (var topicFilter in options.TopicFilters) + { + MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter); } - public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default) + ThrowIfDisposed(); + ThrowIfNotConnected(); + + if (Options.ValidateFeatures) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } + MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion); + } - foreach (var topicFilter in options.TopicFilters) + var unsubscribePacket = MqttUnsubscribePacketFactory.Create(options); + unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + + MqttUnsubAckPacket unsubAckPacket; + if (cancellationToken.CanBeCanceled) + { + unsubAckPacket = await Request(unsubscribePacket, cancellationToken).ConfigureAwait(false); + } + else + { + using (var timeout = new CancellationTokenSource(Options.Timeout)) { - MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter); + unsubAckPacket = await Request(unsubscribePacket, timeout.Token).ConfigureAwait(false); } + } - ThrowIfDisposed(); - ThrowIfNotConnected(); + return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket); + } - if (Options.ValidateFeatures) - { - MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion); - } + protected override void Dispose(bool disposing) + { + if (disposing) + { + Cleanup(); + } - var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options); - unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + base.Dispose(disposing); + } - MqttUnsubAckPacket unsubAckPacket; - if (cancellationToken.CanBeCanceled) + Task AcknowledgeReceivedPublishPacket(MqttApplicationMessageReceivedEventArgs eventArgs, CancellationToken cancellationToken) + { + switch (eventArgs.PublishPacket.QualityOfServiceLevel) + { + case MqttQualityOfServiceLevel.AtMostOnce: { - unsubAckPacket = await Request(unsubscribePacket, cancellationToken).ConfigureAwait(false); + // no response required + break; } - else + case MqttQualityOfServiceLevel.AtLeastOnce: { - using (var timeout = new CancellationTokenSource(Options.Timeout)) + if (!eventArgs.ProcessingFailed) { - unsubAckPacket = await Request(unsubscribePacket, timeout.Token).ConfigureAwait(false); + var pubAckPacket = MqttPubAckPacketFactory.Create(eventArgs); + return Send(pubAckPacket, cancellationToken); } - } - return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket); - } + break; + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + if (!eventArgs.ProcessingFailed) + { + var pubRecPacket = MqttPubRecPacketFactory.Create(eventArgs); + return Send(pubRecPacket, cancellationToken); + } - protected override void Dispose(bool disposing) - { - if (disposing) + break; + } + default: { - Cleanup(); + throw new MqttProtocolViolationException("Received a not supported QoS level."); } - - base.Dispose(disposing); } - Task AcknowledgeReceivedPublishPacket(MqttApplicationMessageReceivedEventArgs eventArgs, CancellationToken cancellationToken) + return CompletedTask.Instance; + } + + async Task Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken) + { + MqttClientConnectResult result; + + try { - switch (eventArgs.PublishPacket.QualityOfServiceLevel) + var connectPacket = MqttConnectPacketFactory.Create(options); + await Send(connectPacket, cancellationToken).ConfigureAwait(false); + + var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false); + + switch (receivedPacket) { - case MqttQualityOfServiceLevel.AtMostOnce: + case MqttConnAckPacket connAckPacket: { - // no response required + result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion); break; } - case MqttQualityOfServiceLevel.AtLeastOnce: + case MqttAuthPacket _: { - if (!eventArgs.ProcessingFailed) - { - var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs); - return Send(pubAckPacket, cancellationToken); - } - - break; + throw new NotSupportedException("Extended authentication handler is not yet supported"); } - case MqttQualityOfServiceLevel.ExactlyOnce: + case null: { - if (!eventArgs.ProcessingFailed) - { - var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs); - return Send(pubRecPacket, cancellationToken); - } - - break; + throw new MqttCommunicationException("Connection closed."); } default: { - throw new MqttProtocolViolationException("Received a not supported QoS level."); + throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket})."); } } - - return CompletedTask.Instance; } - - async Task Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken) + catch (Exception exception) { - MqttClientConnectResult result; - - try - { - var connectPacket = MqttPacketFactories.Connect.Create(options); - await Send(connectPacket, cancellationToken).ConfigureAwait(false); - - var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false); + throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null); + } - switch (receivedPacket) - { - case MqttConnAckPacket connAckPacket: - { - result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion); - break; - } - case MqttAuthPacket _: - { - throw new NotSupportedException("Extended authentication handler is not yet supported"); - } - case null: - { - throw new MqttCommunicationException("Connection closed."); - } - default: - { - throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket})."); - } - } - } - catch (Exception exception) + // This is no feature. It is basically a backward compatibility option and should be removed in the future. + // The client should not throw any exception if the transport layer connection was successful and the server + // did send a proper ACK packet with a non success response. + if (options.ThrowOnNonSuccessfulConnectResponse) + { + if (result.ResultCode != MqttClientConnectResultCode.Success) { - throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null); + _logger.Warning( + "Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options."); + throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result); } + } - // This is no feature. It is basically a backward compatibility option and should be removed in the future. - // The client should not throw any exception if the transport layer connection was successful and the server - // did send a proper ACK packet with a non success response. - if (options.ThrowOnNonSuccessfulConnectResponse) - { - if (result.ResultCode != MqttClientConnectResultCode.Success) - { - _logger.Warning( - "Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options."); - throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result); - } - } + _logger.Verbose("Authenticated MQTT connection with server established."); - _logger.Verbose("Authenticated MQTT connection with server established."); + return result; + } - return result; + void Cleanup() + { + try + { + _mqttClientAlive?.Cancel(false); } - - void Cleanup() + finally { - try - { - _mqttClientAlive?.Cancel(false); - } - finally - { - _mqttClientAlive?.Dispose(); - _mqttClientAlive = null; + _mqttClientAlive?.Dispose(); + _mqttClientAlive = null; - _publishPacketReceiverQueue?.Dispose(); - _publishPacketReceiverQueue = null; + _publishPacketReceiverQueue?.Dispose(); + _publishPacketReceiverQueue = null; - _adapter?.Dispose(); - _adapter = null; + _adapter?.Dispose(); + _adapter = null; - _packetDispatcher?.Dispose(); - _packetDispatcher = null; - } + _packetDispatcher?.Dispose(); + _packetDispatcher = null; } + } - MqttClientConnectionStatus CompareExchangeConnectionStatus(MqttClientConnectionStatus value, MqttClientConnectionStatus comparand) - { - return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand); - } - - async Task ConnectInternal(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) - { - 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"); - - _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); - } + MqttClientConnectionStatus CompareExchangeConnectionStatus(MqttClientConnectionStatus value, MqttClientConnectionStatus comparand) + { + return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand); + } - return connectResult; - } - } + async Task ConnectInternal(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) + { + var backgroundCancellationToken = _mqttClientAlive.Token; - async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) + using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) { - TryInitiateDisconnect(); + _logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions); + await channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false); + _logger.Verbose("Connection with server established"); - try - { - if (_adapter != null) - { - _logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout); + _publishPacketReceiverQueue?.Dispose(); + _publishPacketReceiverQueue = new AsyncQueue(); - using (var timeout = new CancellationTokenSource(Options.Timeout)) - { - await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); - } - } - - _logger.Verbose("Disconnected from adapter."); - } - catch (Exception adapterException) + var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false); + if (connectResult.ResultCode == MqttClientConnectResultCode.Success) { - _logger.Warning(adapterException, "Error while disconnecting from adapter."); + _publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken); + _packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken); } - try - { - _packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception)); - - var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger); - var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger); - var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger); - - await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false); - } - catch (Exception innerException) - { - _logger.Warning(innerException, "Error while waiting for internal tasks."); - } - finally - { - Cleanup(); - _cleanDisconnectInitiated = false; - CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnected, MqttClientConnectionStatus.Disconnecting); - - _logger.Info("Disconnected."); - - var eventArgs = new MqttClientDisconnectedEventArgs( - clientWasConnected, - connectResult, - (MqttClientDisconnectReason)_disconnectReason, - _disconnectReasonString, - _disconnectUserProperties, - exception); - - // This handler must be executed in a new thread to prevent a deadlock - // that may occur when attempting to reconnect within that handler, etc. - Task.Run(() => _events.DisconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger); - } + return connectResult; } + } - Task DisconnectInternal(Task sender, Exception exception, MqttClientConnectResult connectResult) - { - var clientWasConnected = IsConnected; - - return !DisconnectIsPendingOrFinished() ? - DisconnectCore(sender, exception, connectResult, clientWasConnected) - : CompletedTask.Instance; - } + async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) + { + TryInitiateDisconnect(); - bool DisconnectIsPendingOrFinished() + try { - var connectionStatus = (MqttClientConnectionStatus)_connectionStatus; - - do + if (_adapter != null) { - switch (connectionStatus) + _logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout); + + using (var timeout = new CancellationTokenSource(Options.Timeout)) { - case MqttClientConnectionStatus.Disconnected: - case MqttClientConnectionStatus.Disconnecting: - return true; - case MqttClientConnectionStatus.Connected: - case MqttClientConnectionStatus.Connecting: - // This will compare the _connectionStatus to old value and set it to "MqttClientConnectionStatus.Disconnecting" afterwards. - // So the first caller will get a "false" and all subsequent ones will get "true". - var curStatus = CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnecting, connectionStatus); - if (curStatus == connectionStatus) - { - return false; - } - - connectionStatus = curStatus; - break; + await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); } - } while (true); - } + } - void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket) + _logger.Verbose("Disconnected from adapter."); + } + catch (Exception adapterException) { - try - { - _publishPacketReceiverQueue.Enqueue(publishPacket); - } - catch (Exception exception) - { - _logger.Error(exception, "Error while queueing application message."); - } + _logger.Warning(adapterException, "Error while disconnecting from adapter."); } - async Task HandleReceivedApplicationMessage(MqttPublishPacket publishPacket) + try { - var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); - var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket); - await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + _packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception)); - return eventArgs; - } + var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger); + var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger); + var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger); - Task OnConnected(MqttClientConnectResult connectResult) + await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false); + } + catch (Exception innerException) { - if (!_events.ConnectedEvent.HasHandlers) - { - return CompletedTask.Instance; - } - - var eventArgs = new MqttClientConnectedEventArgs(connectResult); - return _events.ConnectedEvent.InvokeAsync(eventArgs); + _logger.Warning(innerException, "Error while waiting for internal tasks."); } - - Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket) + finally { - var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler; - return extendedAuthenticationExchangeHandler != null ? extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this)) : CompletedTask.Instance; + Cleanup(); + _cleanDisconnectInitiated = false; + CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnected, MqttClientConnectionStatus.Disconnecting); + + _logger.Info("Disconnected."); + + var eventArgs = new MqttClientDisconnectedEventArgs( + clientWasConnected, + connectResult, + (MqttClientDisconnectReason)_disconnectReason, + _disconnectReasonString, + _disconnectUserProperties, + exception); + + // This handler must be executed in a new thread to prevent a deadlock + // that may occur when attempting to reconnect within that handler, etc. + Task.Run(() => _events.DisconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger); } + } - Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket) - { - _disconnectReason = (int)disconnectPacket.ReasonCode; - _disconnectReasonString = disconnectPacket.ReasonString; - _disconnectUserProperties = disconnectPacket.UserProperties; - _unexpectedDisconnectPacket = disconnectPacket; + Task DisconnectInternal(Task sender, Exception exception, MqttClientConnectResult connectResult) + { + var clientWasConnected = IsConnected; - // Also dispatch disconnect to waiting threads to generate a proper exception. - _packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket, null)); + return !DisconnectIsPendingOrFinished() ? DisconnectCore(sender, exception, connectResult, clientWasConnected) : CompletedTask.Instance; + } - return DisconnectInternal(_packetReceiverTask, null, null); - } + bool DisconnectIsPendingOrFinished() + { + var connectionStatus = (MqttClientConnectionStatus)_connectionStatus; - async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) + do { - while (!cancellationToken.IsCancellationRequested) + switch (connectionStatus) { - try - { - var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); - if (!publishPacketDequeueResult.IsSuccess) + case MqttClientConnectionStatus.Disconnected: + case MqttClientConnectionStatus.Disconnecting: + return true; + case MqttClientConnectionStatus.Connected: + case MqttClientConnectionStatus.Connecting: + // This will compare the _connectionStatus to old value and set it to "MqttClientConnectionStatus.Disconnecting" afterwards. + // So the first caller will get a "false" and all subsequent ones will get "true". + var curStatus = CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnecting, connectionStatus); + if (curStatus == connectionStatus) { - return; + return false; } - var publishPacket = publishPacketDequeueResult.Item; - var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false); - - if (eventArgs.AutoAcknowledge) - { - await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false); - } - } - catch (ObjectDisposedException) - { - } - catch (OperationCanceledException) - { - } - catch (Exception exception) - { - _logger.Error(exception, "Error while handling application message"); - } + connectionStatus = curStatus; + break; } - } + } while (true); + } - Task ProcessReceivedPubRecPacket(MqttPubRecPacket pubRecPacket, CancellationToken cancellationToken) + void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket) + { + try { - if (_packetDispatcher.TryDispatch(pubRecPacket)) - { - return CompletedTask.Instance; - } - - // The packet is unknown, probably due to a restart of the client. - // So we send this to the server to trigger a full resend of the message. - var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound); - return Send(pubRelPacket, cancellationToken); + _publishPacketReceiverQueue.Enqueue(publishPacket); } - - Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) + catch (Exception exception) { - var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); - return Send(pubCompPacket, cancellationToken); + _logger.Error(exception, "Error while queueing application message."); } + } - async Task PublishAtLeastOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) - { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + async Task HandleReceivedApplicationMessage(MqttPublishPacket publishPacket) + { + var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); + var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket); + await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + + return eventArgs; + } - var pubAckPacket = await Request(publishPacket, cancellationToken).ConfigureAwait(false); - return MqttClientResultFactory.PublishResult.Create(pubAckPacket); + Task OnConnected(MqttClientConnectResult connectResult) + { + if (!_events.ConnectedEvent.HasHandlers) + { + return CompletedTask.Instance; } - async Task PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + var eventArgs = new MqttClientConnectedEventArgs(connectResult); + return _events.ConnectedEvent.InvokeAsync(eventArgs); + } + + Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket) + { + var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler; + return extendedAuthenticationExchangeHandler != null + ? extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this)) + : CompletedTask.Instance; + } + + Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket) + { + _disconnectReason = (int)disconnectPacket.ReasonCode; + _disconnectReasonString = disconnectPacket.ReasonString; + _disconnectUserProperties = disconnectPacket.UserProperties; + _unexpectedDisconnectPacket = disconnectPacket; + + // Also dispatch disconnect to waiting threads to generate a proper exception. + _packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket)); + + return DisconnectInternal(_packetReceiverTask, null, null); + } + + async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) { try { - // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] - await Send(publishPacket, cancellationToken).ConfigureAwait(false); + var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); + if (!publishPacketDequeueResult.IsSuccess) + { + return; + } - return MqttClientResultFactory.PublishResult.Create(null); - } - catch (Exception exception) - { - // We have to check if the server has sent a disconnect packet in response to the published message. - // Since we are in QoS 0 we do not get a direct response via an PUBACK packet and thus basically no - // feedback at all. - var localUnexpectedDisconnectPacket = _unexpectedDisconnectPacket; + var publishPacket = publishPacketDequeueResult.Item; + var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false); - if (localUnexpectedDisconnectPacket != null) + if (eventArgs.AutoAcknowledge) { - throw new MqttClientUnexpectedDisconnectReceivedException(localUnexpectedDisconnectPacket, exception); + await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false); } - - throw; + } + catch (ObjectDisposedException) + { + } + catch (OperationCanceledException) + { + } + catch (Exception exception) + { + _logger.Error(exception, "Error while handling application message"); } } + } - async Task PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + Task ProcessReceivedPubRecPacket(MqttPubRecPacket pubRecPacket, CancellationToken cancellationToken) + { + if (_packetDispatcher.TryDispatch(pubRecPacket)) { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + return CompletedTask.Instance; + } - var pubRecPacket = await Request(publishPacket, cancellationToken).ConfigureAwait(false); + // The packet is unknown, probably due to a restart of the client. + // So we send this to the server to trigger a full resend of the message. + var pubRelPacket = MqttPubRelPacketFactory.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound); + return Send(pubRelPacket, cancellationToken); + } - var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); + Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) + { + var pubCompPacket = MqttPubCompPacketFactory.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); + return Send(pubCompPacket, cancellationToken); + } - var pubCompPacket = await Request(pubRelPacket, cancellationToken).ConfigureAwait(false); + async Task PublishAtLeastOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket); - } + var pubAckPacket = await Request(publishPacket, cancellationToken).ConfigureAwait(false); + return MqttClientResultFactory.PublishResult.Create(pubAckPacket); + } - async Task Receive(CancellationToken cancellationToken) + async Task PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + { + try { - var packetTask = _adapter.ReceivePacketAsync(cancellationToken); + // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] + await Send(publishPacket, cancellationToken).ConfigureAwait(false); - MqttPacket packet; - if (packetTask.IsCompleted) - { - packet = packetTask.Result; - } - else + return MqttClientResultFactory.PublishResult.Create(null); + } + catch (Exception exception) + { + // We have to check if the server has sent a disconnect packet in response to the published message. + // Since we are in QoS 0 we do not get a direct response via an PUBACK packet and thus basically no + // feedback at all. + var localUnexpectedDisconnectPacket = _unexpectedDisconnectPacket; + + if (localUnexpectedDisconnectPacket != null) { - packet = await packetTask.ConfigureAwait(false); + throw new MqttClientUnexpectedDisconnectReceivedException(localUnexpectedDisconnectPacket, exception); } - return packet; + throw; } + } - async Task ReceivePacketsLoop(CancellationToken cancellationToken) - { - try - { - _logger.Verbose("Start receiving packets"); + async Task PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - while (!cancellationToken.IsCancellationRequested) - { - var packet = await Receive(cancellationToken).ConfigureAwait(false); + var pubRecPacket = await Request(publishPacket, cancellationToken).ConfigureAwait(false); - if (cancellationToken.IsCancellationRequested) - { - return; - } + var pubRelPacket = MqttPubRelPacketFactory.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); - if (packet == null) - { - await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false); + var pubCompPacket = await Request(pubRelPacket, cancellationToken).ConfigureAwait(false); - return; - } + return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket); + } - await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false); - } - } - catch (Exception exception) + async Task Receive(CancellationToken cancellationToken) + { + var packetTask = _adapter.ReceivePacketAsync(cancellationToken); + + MqttPacket packet; + if (packetTask.IsCompleted) + { + packet = packetTask.Result; + } + else + { + packet = await packetTask.ConfigureAwait(false); + } + + return packet; + } + + async Task ReceivePacketsLoop(CancellationToken cancellationToken) + { + try + { + _logger.Verbose("Start receiving packets"); + + while (!cancellationToken.IsCancellationRequested) { - if (_cleanDisconnectInitiated) + var packet = await Receive(cancellationToken).ConfigureAwait(false); + + if (cancellationToken.IsCancellationRequested) { return; } - if (exception is AggregateException aggregateException) + if (packet == null) { - exception = aggregateException.GetBaseException(); - } + await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false); - if (exception is OperationCanceledException) - { - } - else if (exception is MqttCommunicationException) - { - _logger.Warning(exception, "Communication error while receiving packets"); - } - else - { - _logger.Error(exception, "Error while receiving packets"); + return; } - // The packet dispatcher is set to null when the client is being disposed so it may already being gone! - _packetDispatcher?.FailAll(exception); + await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception exception) + { + if (_cleanDisconnectInitiated) + { + return; + } - await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false); + if (exception is AggregateException aggregateException) + { + exception = aggregateException.GetBaseException(); + } + + if (exception is OperationCanceledException) + { } - finally + else if (exception is MqttCommunicationException) { - _logger.Verbose("Stopped receiving packets"); + _logger.Warning(exception, "Communication error while receiving packets"); } + else + { + _logger.Error(exception, "Error while receiving packets"); + } + + // The packet dispatcher is set to null when the client is being disposed so it may already being gone! + _packetDispatcher?.FailAll(exception); + + await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false); } + finally + { + _logger.Verbose("Stopped receiving packets"); + } + } + + async Task Request(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket + { + cancellationToken.ThrowIfCancellationRequested(); - async Task Request(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket + ushort packetIdentifier = 0; + if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier) { - cancellationToken.ThrowIfCancellationRequested(); + packetIdentifier = packetWithIdentifier.PacketIdentifier; + } - ushort packetIdentifier = 0; - if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier) + using (var packetAwaitable = _packetDispatcher.AddAwaitable(packetIdentifier)) + { + try { - packetIdentifier = packetWithIdentifier.PacketIdentifier; + await Send(requestPacket, cancellationToken).ConfigureAwait(false); } - - using (var packetAwaitable = _packetDispatcher.AddAwaitable(packetIdentifier)) + catch (Exception exception) { - 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); - } + _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; } } + } - Task Send(MqttPacket packet, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + Task Send(MqttPacket packet, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - _lastPacketSentTimestamp = DateTime.UtcNow; + _lastPacketSentTimestamp = DateTime.UtcNow; - return _adapter.SendPacketAsync(packet, cancellationToken); - } + return _adapter.SendPacketAsync(packet, cancellationToken); + } - void ThrowIfConnected(string message) + void ThrowIfConnected(string message) + { + if (IsConnected) { - if (IsConnected) - { - throw new InvalidOperationException(message); - } + throw new InvalidOperationException(message); } + } - void ThrowIfNotConnected() + void ThrowIfNotConnected() + { + if (!IsConnected) { - if (!IsConnected) - { - ThrowNotConnected(); - } + ThrowNotConnected(); } + } - static void ThrowIfOptionsInvalid(MqttClientOptions options) + static void ThrowIfOptionsInvalid(MqttClientOptions options) + { + if (options == null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (options.ChannelOptions == null) - { - throw new ArgumentException("ChannelOptions are not set."); - } + throw new ArgumentNullException(nameof(options)); + } - if (options.ValidateFeatures) - { - MqttClientOptionsValidator.ThrowIfNotSupported(options); - } + if (options.ChannelOptions == null) + { + throw new ArgumentException("ChannelOptions are not set."); } - static void ThrowNotConnected() + if (options.ValidateFeatures) { - throw new MqttClientNotConnectedException(); + MqttClientOptionsValidator.ThrowIfNotSupported(options); } + } + + static void ThrowNotConnected() + { + throw new MqttClientNotConnectedException(); + } - void TryInitiateDisconnect() + void TryInitiateDisconnect() + { + lock (_disconnectLock) { - lock (_disconnectLock) + try { - try - { - _mqttClientAlive?.Cancel(false); - } - catch (Exception exception) - { - _logger.Warning(exception, "Error while initiating disconnect"); - } + _mqttClientAlive?.Cancel(false); + } + catch (Exception exception) + { + _logger.Warning(exception, "Error while initiating disconnect"); } } + } - async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken) + async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken) + { + try { - try + switch (packet) { - switch (packet) + case MqttPublishPacket publishPacket: + EnqueueReceivedPublishPacket(publishPacket); + break; + case MqttPubRecPacket pubRecPacket: + await ProcessReceivedPubRecPacket(pubRecPacket, cancellationToken).ConfigureAwait(false); + break; + case MqttPubRelPacket pubRelPacket: + await ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false); + break; + case MqttDisconnectPacket disconnectPacket: + await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false); + break; + case MqttAuthPacket authPacket: + await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false); + break; + case MqttPingRespPacket _: + _packetDispatcher.TryDispatch(packet); + break; + case MqttPingReqPacket _: + throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a client to the server only."); + default: { - case MqttPublishPacket publishPacket: - EnqueueReceivedPublishPacket(publishPacket); - break; - case MqttPubRecPacket pubRecPacket: - await ProcessReceivedPubRecPacket(pubRecPacket, cancellationToken).ConfigureAwait(false); - break; - case MqttPubRelPacket pubRelPacket: - await ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false); - break; - case MqttDisconnectPacket disconnectPacket: - await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false); - break; - case MqttAuthPacket authPacket: - await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false); - break; - case MqttPingRespPacket _: - _packetDispatcher.TryDispatch(packet); - break; - case MqttPingReqPacket _: - throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a client to the server only."); - default: + if (!_packetDispatcher.TryDispatch(packet)) { - if (!_packetDispatcher.TryDispatch(packet)) - { - throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); - } - - break; + throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); } + + break; } } - catch (Exception exception) + } + catch (Exception exception) + { + if (_cleanDisconnectInitiated) { - if (_cleanDisconnectInitiated) - { - return; - } + return; + } - switch (exception) - { - case OperationCanceledException _: - break; - case MqttCommunicationException _: - _logger.Warning(exception, "Communication error while receiving packets"); - break; - default: - _logger.Error(exception, "Error while processing received {0} packet", packet.GetType().Name); - break; - } + switch (exception) + { + case OperationCanceledException _: + break; + case MqttCommunicationException _: + _logger.Warning(exception, "Communication error while receiving packets"); + break; + default: + _logger.Error(exception, "Error while processing received {0} packet", packet.GetType().Name); + break; + } - // The packet dispatcher may already be gone due to disconnect etc.! - _packetDispatcher?.FailAll(exception); + // The packet dispatcher may already be gone due to disconnect etc.! + _packetDispatcher?.FailAll(exception); - await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false); - } + await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false); } + } - async Task TrySendKeepAliveMessages(CancellationToken cancellationToken) + async Task TrySendKeepAliveMessages(CancellationToken cancellationToken) + { + try { - try - { - _logger.Verbose("Start sending keep alive packets"); + _logger.Verbose("Start sending keep alive packets"); - var keepAlivePeriod = Options.KeepAlivePeriod; + var keepAlivePeriod = Options.KeepAlivePeriod; - while (!cancellationToken.IsCancellationRequested) - { - // Values described here: [MQTT-3.1.2-24]. - var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp; + while (!cancellationToken.IsCancellationRequested) + { + // Values described here: [MQTT-3.1.2-24]. + var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp; - if (timeWithoutPacketSent > keepAlivePeriod) + if (timeWithoutPacketSent > keepAlivePeriod) + { + using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { - using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) - { - timeoutCancellationTokenSource.CancelAfter(Options.Timeout); - await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false); - } + timeoutCancellationTokenSource.CancelAfter(Options.Timeout); + await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false); } - - // Wait a fixed time in all cases. Calculation of the remaining time is complicated - // due to some edge cases and was buggy in the past. Now we wait several ms because the - // min keep alive value is one second so that the server will wait 1.5 seconds for a PING - // packet. - await Task.Delay(250, cancellationToken).ConfigureAwait(false); } + + // Wait a fixed time in all cases. Calculation of the remaining time is complicated + // due to some edge cases and was buggy in the past. Now we wait several ms because the + // min keep alive value is one second so that the server will wait 1.5 seconds for a PING + // packet. + await Task.Delay(250, cancellationToken).ConfigureAwait(false); } - catch (Exception exception) + } + catch (Exception exception) + { + if (_cleanDisconnectInitiated) { - if (_cleanDisconnectInitiated) - { - return; - } - - switch (exception) - { - case OperationCanceledException _: - return; - case MqttCommunicationException _: - _logger.Warning(exception, "Communication error while sending/receiving keep alive packets"); - break; - default: - _logger.Error(exception, "Error exception while sending/receiving keep alive packets"); - break; - } - - await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false); + return; } - finally + + switch (exception) { - _logger.Verbose("Stopped sending keep alive packets"); + case OperationCanceledException _: + return; + case MqttCommunicationException _: + _logger.Warning(exception, "Communication error while sending/receiving keep alive packets"); + break; + default: + _logger.Error(exception, "Error exception while sending/receiving keep alive packets"); + break; } + + await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false); + } + finally + { + _logger.Verbose("Stopped sending keep alive packets"); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttClientConnectionStatus.cs b/Source/MQTTnet/Client/MqttClientConnectionStatus.cs index 65833ae42..6abb565db 100644 --- a/Source/MQTTnet/Client/MqttClientConnectionStatus.cs +++ b/Source/MQTTnet/Client/MqttClientConnectionStatus.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.Client +namespace MQTTnet.Client; + +public enum MqttClientConnectionStatus { - public enum MqttClientConnectionStatus - { - Disconnected, - Disconnecting, - Connected, - Connecting - } + Disconnected, + Disconnecting, + Connected, + Connecting } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index da2f2995b..1b0023f8a 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -10,190 +10,189 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public static class MqttClientExtensions { - public static class MqttClientExtensions + public static Task DisconnectAsync( + this IMqttClient client, + MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection, + string reasonString = null, + uint sessionExpiryInterval = 0, + List userProperties = null, + CancellationToken cancellationToken = default) { - public static Task DisconnectAsync( - this IMqttClient client, - MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection, - string reasonString = null, - uint sessionExpiryInterval = 0, - List userProperties = null, - CancellationToken cancellationToken = default) + if (client == null) + { + throw new ArgumentNullException(nameof(client)); + } + + var disconnectOptions = new MqttClientDisconnectOptions { - if (client == null) - { - throw new ArgumentNullException(nameof(client)); - } + Reason = reason, + ReasonString = reasonString, + SessionExpiryInterval = sessionExpiryInterval, + UserProperties = userProperties + }; - var disconnectOptions = new MqttClientDisconnectOptions - { - Reason = reason, - ReasonString = reasonString, - SessionExpiryInterval = sessionExpiryInterval, - UserProperties = userProperties - }; + return client.DisconnectAsync(disconnectOptions, cancellationToken); + } - return client.DisconnectAsync(disconnectOptions, cancellationToken); + public static Task PublishBinaryAsync( + this IMqttClient mqttClient, + string topic, + IEnumerable payload = null, + MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, + bool retain = false, + CancellationToken cancellationToken = default) + { + if (mqttClient == null) + { + throw new ArgumentNullException(nameof(mqttClient)); } - public static Task PublishBinaryAsync( - this IMqttClient mqttClient, - string topic, - IEnumerable payload = null, - MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, - bool retain = false, - CancellationToken cancellationToken = default) + if (topic == null) { - if (mqttClient == null) - { - throw new ArgumentNullException(nameof(mqttClient)); - } + throw new ArgumentNullException(nameof(topic)); + } - if (topic == null) - { - throw new ArgumentNullException(nameof(topic)); - } + var applicationMessage = new MqttApplicationMessageBuilder().WithTopic(topic) + .WithPayload(payload) + .WithRetainFlag(retain) + .WithQualityOfServiceLevel(qualityOfServiceLevel) + .Build(); - var applicationMessage = new MqttApplicationMessageBuilder().WithTopic(topic) - .WithPayload(payload) - .WithRetainFlag(retain) - .WithQualityOfServiceLevel(qualityOfServiceLevel) - .Build(); + return mqttClient.PublishAsync(applicationMessage, cancellationToken); + } - return mqttClient.PublishAsync(applicationMessage, cancellationToken); - } + public static Task PublishStringAsync( + this IMqttClient mqttClient, + string topic, + string payload = null, + MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, + bool retain = false, + CancellationToken cancellationToken = default) + { + var payloadBuffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); + return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken); + } - public static Task PublishStringAsync( - this IMqttClient mqttClient, - string topic, - string payload = null, - MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, - bool retain = false, - CancellationToken cancellationToken = default) + public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default) + { + if (client.Options == null) { - var payloadBuffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); - return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken); + throw new InvalidOperationException( + "The MQTT client was not connected before. A reconnect is only permitted when the client was already connected or at least tried to."); } - public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default) - { - if (client.Options == null) - { - throw new InvalidOperationException( - "The MQTT client was not connected before. A reconnect is only permitted when the client was already connected or at least tried to."); - } + return client.ConnectAsync(client.Options, cancellationToken); + } - return client.ConnectAsync(client.Options, cancellationToken); + public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data) + { + if (client == null) + { + throw new ArgumentNullException(nameof(client)); } - public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data) - { - if (client == null) - { - throw new ArgumentNullException(nameof(client)); - } + return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None); + } - return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None); + public static Task SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default) + { + if (mqttClient == null) + { + throw new ArgumentNullException(nameof(mqttClient)); } - public static Task SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default) + if (topicFilter == null) { - if (mqttClient == null) - { - throw new ArgumentNullException(nameof(mqttClient)); - } + throw new ArgumentNullException(nameof(topicFilter)); + } - if (topicFilter == null) - { - throw new ArgumentNullException(nameof(topicFilter)); - } + var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build(); - var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build(); + return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); + } - return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); + public static Task SubscribeAsync( + this IMqttClient mqttClient, + string topic, + MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, + CancellationToken cancellationToken = default) + { + if (mqttClient == null) + { + throw new ArgumentNullException(nameof(mqttClient)); } - public static Task SubscribeAsync( - this IMqttClient mqttClient, - string topic, - MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, - CancellationToken cancellationToken = default) + if (topic == null) { - if (mqttClient == null) - { - throw new ArgumentNullException(nameof(mqttClient)); - } + throw new ArgumentNullException(nameof(topic)); + } - if (topic == null) - { - throw new ArgumentNullException(nameof(topic)); - } + var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build(); - var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build(); + return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); + } - return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); + public static async Task TryDisconnectAsync( + this IMqttClient client, + MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection, + string reasonString = null) + { + if (client == null) + { + throw new ArgumentNullException(nameof(client)); } - public static async Task TryDisconnectAsync( - this IMqttClient client, - MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection, - string reasonString = null) + try + { + await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false); + return true; + } + catch { - if (client == null) - { - throw new ArgumentNullException(nameof(client)); - } + // Ignore all errors. + } - try - { - await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false); - return true; - } - catch - { - // Ignore all errors. - } + return false; + } - return false; + public static async Task TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default) + { + if (client == null) + { + throw new ArgumentNullException(nameof(client)); } - public static async Task TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default) + try + { + await client.PingAsync(cancellationToken).ConfigureAwait(false); + return true; + } + catch { - if (client == null) - { - throw new ArgumentNullException(nameof(client)); - } + // Ignore errors. + } - try - { - await client.PingAsync(cancellationToken).ConfigureAwait(false); - return true; - } - catch - { - // Ignore errors. - } + return false; + } - return false; + public static Task UnsubscribeAsync(this IMqttClient mqttClient, string topic, CancellationToken cancellationToken = default) + { + if (mqttClient == null) + { + throw new ArgumentNullException(nameof(mqttClient)); } - public static Task UnsubscribeAsync(this IMqttClient mqttClient, string topic, CancellationToken cancellationToken = default) + if (topic == null) { - if (mqttClient == null) - { - throw new ArgumentNullException(nameof(mqttClient)); - } - - if (topic == null) - { - throw new ArgumentNullException(nameof(topic)); - } + throw new ArgumentNullException(nameof(topic)); + } - var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build(); + var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build(); - return mqttClient.UnsubscribeAsync(unsubscribeOptions, cancellationToken); - } + return mqttClient.UnsubscribeAsync(unsubscribeOptions, cancellationToken); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs index 21cde6463..e03002538 100644 --- a/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs +++ b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs @@ -2,36 +2,35 @@ // 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.Client +namespace MQTTnet.Client; + +public sealed class MqttPacketIdentifierProvider { - public sealed class MqttPacketIdentifierProvider - { - readonly object _syncRoot = new object(); + readonly object _syncRoot = new(); - ushort _value; + ushort _value; - public void Reset() + public ushort GetNextPacketIdentifier() + { + lock (_syncRoot) { - lock (_syncRoot) + _value++; + + if (_value == 0) { - _value = 0; + // As per official MQTT documentation the package identifier should never be 0. + _value = 1; } + + return _value; } + } - public ushort GetNextPacketIdentifier() + public void Reset() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _value++; - - if (_value == 0) - { - // As per official MQTT documentation the package identifier should never be 0. - _value = 1; - } - - return _value; - } + _value = 0; } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/DefaultMqttCertificatesProvider.cs b/Source/MQTTnet/Client/Options/DefaultMqttCertificatesProvider.cs index 7c3949d91..18ecf5e36 100644 --- a/Source/MQTTnet/Client/Options/DefaultMqttCertificatesProvider.cs +++ b/Source/MQTTnet/Client/Options/DefaultMqttCertificatesProvider.cs @@ -5,32 +5,31 @@ using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class DefaultMqttCertificatesProvider : IMqttClientCertificatesProvider { - public sealed class DefaultMqttCertificatesProvider : IMqttClientCertificatesProvider - { - readonly X509Certificate2Collection _certificates; + readonly X509Certificate2Collection _certificates; - public DefaultMqttCertificatesProvider(X509Certificate2Collection certificates) - { - _certificates = certificates; - } + public DefaultMqttCertificatesProvider(X509Certificate2Collection certificates) + { + _certificates = certificates; + } - public DefaultMqttCertificatesProvider(IEnumerable certificates) + public DefaultMqttCertificatesProvider(IEnumerable certificates) + { + if (certificates != null) { - if (certificates != null) + _certificates = new X509Certificate2Collection(); + foreach (var certificate in certificates) { - _certificates = new X509Certificate2Collection(); - foreach (var certificate in certificates) - { - _certificates.Add(certificate); - } + _certificates.Add(certificate); } } + } - public X509CertificateCollection GetCertificates() - { - return _certificates; - } + public X509CertificateCollection GetCertificates() + { + return _certificates; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/IMqttClientCertificatesProvider.cs b/Source/MQTTnet/Client/Options/IMqttClientCertificatesProvider.cs index b90017392..625669846 100644 --- a/Source/MQTTnet/Client/Options/IMqttClientCertificatesProvider.cs +++ b/Source/MQTTnet/Client/Options/IMqttClientCertificatesProvider.cs @@ -2,10 +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.Client +using System.Security.Cryptography.X509Certificates; + +namespace MQTTnet.Client; + +public interface IMqttClientCertificatesProvider { - public interface IMqttClientCertificatesProvider - { - System.Security.Cryptography.X509Certificates.X509CertificateCollection GetCertificates(); - } + X509CertificateCollection GetCertificates(); } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs b/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs index 11ecf16d0..461d62e1f 100644 --- a/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs +++ b/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.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.Client +namespace MQTTnet.Client; + +public interface IMqttClientChannelOptions { - public interface IMqttClientChannelOptions - { - MqttClientTlsOptions TlsOptions { get; } - } -} + MqttClientTlsOptions TlsOptions { get; } +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/IMqttClientCredentialsProvider.cs b/Source/MQTTnet/Client/Options/IMqttClientCredentialsProvider.cs index 382f91e03..91415aaab 100644 --- a/Source/MQTTnet/Client/Options/IMqttClientCredentialsProvider.cs +++ b/Source/MQTTnet/Client/Options/IMqttClientCredentialsProvider.cs @@ -2,12 +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.Client -{ - public interface IMqttClientCredentialsProvider - { - string GetUserName(MqttClientOptions clientOptions); +namespace MQTTnet.Client; - byte[] GetPassword(MqttClientOptions clientOptions); - } +public interface IMqttClientCredentialsProvider +{ + byte[] GetPassword(MqttClientOptions clientOptions); + string GetUserName(MqttClientOptions clientOptions); } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientCertificateSelectionEventArgs.cs b/Source/MQTTnet/Client/Options/MqttClientCertificateSelectionEventArgs.cs index f1be293a9..dc25cc588 100644 --- a/Source/MQTTnet/Client/Options/MqttClientCertificateSelectionEventArgs.cs +++ b/Source/MQTTnet/Client/Options/MqttClientCertificateSelectionEventArgs.cs @@ -5,32 +5,31 @@ using System; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientCertificateSelectionEventArgs : EventArgs { - public sealed class MqttClientCertificateSelectionEventArgs : EventArgs + public MqttClientCertificateSelectionEventArgs( + string targetHost, + X509CertificateCollection localCertificates, + X509Certificate remoteCertificate, + string[] acceptableIssuers, + MqttClientTcpOptions tcpOptions) { - public MqttClientCertificateSelectionEventArgs( - string targetHost, - X509CertificateCollection localCertificates, - X509Certificate remoteCertificate, - string[] acceptableIssuers, - MqttClientTcpOptions tcpOptions) - { - TargetHost = targetHost; - LocalCertificates = localCertificates; - RemoveCertificate = remoteCertificate; - AcceptableIssuers = acceptableIssuers; - TcpOptions = tcpOptions ?? throw new ArgumentNullException(nameof(tcpOptions)); - } + TargetHost = targetHost; + LocalCertificates = localCertificates; + RemoveCertificate = remoteCertificate; + AcceptableIssuers = acceptableIssuers; + TcpOptions = tcpOptions ?? throw new ArgumentNullException(nameof(tcpOptions)); + } - public string[] AcceptableIssuers { get; } + public string[] AcceptableIssuers { get; } - public X509CertificateCollection LocalCertificates { get; } + public X509CertificateCollection LocalCertificates { get; } - public X509Certificate RemoveCertificate { get; } + public X509Certificate RemoveCertificate { get; } - public string TargetHost { get; } + public string TargetHost { get; } - public MqttClientTcpOptions TcpOptions { get; } - } + public MqttClientTcpOptions TcpOptions { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientCertificateValidationEventArgs.cs b/Source/MQTTnet/Client/Options/MqttClientCertificateValidationEventArgs.cs index 30bd29664..b76e932b9 100644 --- a/Source/MQTTnet/Client/Options/MqttClientCertificateValidationEventArgs.cs +++ b/Source/MQTTnet/Client/Options/MqttClientCertificateValidationEventArgs.cs @@ -6,24 +6,23 @@ using System.Net.Security; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientCertificateValidationEventArgs : EventArgs { - public sealed class MqttClientCertificateValidationEventArgs : EventArgs + public MqttClientCertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, IMqttClientChannelOptions clientOptions) { - public MqttClientCertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, IMqttClientChannelOptions clientOptions) - { - Certificate = certificate; - Chain = chain; - SslPolicyErrors = sslPolicyErrors; - ClientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); - } + Certificate = certificate; + Chain = chain; + SslPolicyErrors = sslPolicyErrors; + ClientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); + } - public X509Certificate Certificate { get; } + public X509Certificate Certificate { get; } - public X509Chain Chain { get; } + public X509Chain Chain { get; } - public IMqttClientChannelOptions ClientOptions { get; } + public IMqttClientChannelOptions ClientOptions { get; } - public SslPolicyErrors SslPolicyErrors { get; } - } + public SslPolicyErrors SslPolicyErrors { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientCredentials.cs b/Source/MQTTnet/Client/Options/MqttClientCredentials.cs index b2d50a9dc..82e6e07db 100644 --- a/Source/MQTTnet/Client/Options/MqttClientCredentials.cs +++ b/Source/MQTTnet/Client/Options/MqttClientCredentials.cs @@ -2,27 +2,26 @@ // 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.Client +namespace MQTTnet.Client; + +public sealed class MqttClientCredentials : IMqttClientCredentialsProvider { - public sealed class MqttClientCredentials : IMqttClientCredentialsProvider + readonly byte[] _password; + readonly string _userName; + + public MqttClientCredentials(string userName, byte[] password = null) { - readonly string _userName; - readonly byte[] _password; + _userName = userName; + _password = password; + } - public MqttClientCredentials(string userName, byte[] password = null) - { - _userName = userName; - _password = password; - } - - public string GetUserName(MqttClientOptions clientOptions) - { - return _userName; - } + public byte[] GetPassword(MqttClientOptions clientOptions) + { + return _password; + } - public byte[] GetPassword(MqttClientOptions clientOptions) - { - return _password; - } + public string GetUserName(MqttClientOptions clientOptions) + { + return _userName; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientDefaultCertificateValidationHandler.cs b/Source/MQTTnet/Client/Options/MqttClientDefaultCertificateValidationHandler.cs index ffc4c080e..ed0b7c628 100644 --- a/Source/MQTTnet/Client/Options/MqttClientDefaultCertificateValidationHandler.cs +++ b/Source/MQTTnet/Client/Options/MqttClientDefaultCertificateValidationHandler.cs @@ -6,35 +6,34 @@ using System.Net.Security; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientDefaultCertificateValidationHandler { - public sealed class MqttClientDefaultCertificateValidationHandler + public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs) { - public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs) + if (eventArgs.SslPolicyErrors == SslPolicyErrors.None) { - if (eventArgs.SslPolicyErrors == SslPolicyErrors.None) - { - return true; - } + return true; + } - if (eventArgs.Chain.ChainStatus.Any(c => - c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation)) + if (eventArgs.Chain.ChainStatus.Any( + c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation)) + { + if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateRevocationErrors != true) { - if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateRevocationErrors != true) - { - return false; - } + return false; } + } - if (eventArgs.Chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain)) + if (eventArgs.Chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain)) + { + if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateChainErrors != true) { - if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateChainErrors != true) - { - return false; - } + return false; } - - return eventArgs.ClientOptions?.TlsOptions?.AllowUntrustedCertificates == true; } + + return eventArgs.ClientOptions?.TlsOptions?.AllowUntrustedCertificates == true; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs index 38ddbfb44..58783db8f 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs @@ -7,108 +7,107 @@ using MQTTnet.Formatter; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public static class MqttClientOptionsValidator { - public static class MqttClientOptionsValidator + public static void ThrowIfNotSupported(MqttClientOptions options) { - public static void ThrowIfNotSupported(MqttClientOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (options.ProtocolVersion == MqttProtocolVersion.V500) - { - // Everything is supported. - return; - } - - if (options.WillContentType?.Any() == true) - { - Throw(nameof(options.WillContentType)); - } - - if (options.UserProperties?.Any() == true) - { - Throw(nameof(options.UserProperties)); - } - - if (options.RequestProblemInformation) - { - // Since this value is a boolean and true by default, validation would - // require a nullable boolean. - //Throw(nameof(options.RequestProblemInformation)); - } - - if (options.RequestResponseInformation) - { - Throw(nameof(options.RequestResponseInformation)); - } - - if (options.ReceiveMaximum > 0) - { - Throw(nameof(options.ReceiveMaximum)); - } - - if (options.MaximumPacketSize > 0) - { - Throw(nameof(options.MaximumPacketSize)); - } - - // Authentication relevant properties. - - if (options.AuthenticationData?.Any() == true) - { - Throw(nameof(options.AuthenticationData)); - } - - if (options.AuthenticationMethod?.Any() == true) - { - Throw(nameof(options.AuthenticationMethod)); - } - - // Will relevant properties. - - if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified) - { - Throw(nameof(options.WillPayloadFormatIndicator)); - } - - if (options.WillContentType?.Any() == true) - { - Throw(nameof(options.WillContentType)); - } - - if (options.WillCorrelationData?.Any() == true) - { - Throw(nameof(options.WillCorrelationData)); - } - - if (options.WillResponseTopic?.Any() == true) - { - Throw(nameof(options.WillResponseTopic)); - } - - if (options.WillDelayInterval > 0) - { - Throw(nameof(options.WillDelayInterval)); - } - - if (options.WillMessageExpiryInterval > 0) - { - Throw(nameof(options.WillMessageExpiryInterval)); - } - - if (options.WillUserProperties?.Any() == true) - { - Throw(nameof(options.WillUserProperties)); - } - } - - static void Throw(string featureName) - { - throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0."); + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (options.ProtocolVersion == MqttProtocolVersion.V500) + { + // Everything is supported. + return; + } + + if (options.WillContentType?.Any() == true) + { + Throw(nameof(options.WillContentType)); + } + + if (options.UserProperties?.Any() == true) + { + Throw(nameof(options.UserProperties)); + } + + if (options.RequestProblemInformation) + { + // Since this value is a boolean and true by default, validation would + // require a nullable boolean. + //Throw(nameof(options.RequestProblemInformation)); } + + if (options.RequestResponseInformation) + { + Throw(nameof(options.RequestResponseInformation)); + } + + if (options.ReceiveMaximum > 0) + { + Throw(nameof(options.ReceiveMaximum)); + } + + if (options.MaximumPacketSize > 0) + { + Throw(nameof(options.MaximumPacketSize)); + } + + // Authentication relevant properties. + + if (options.AuthenticationData?.Any() == true) + { + Throw(nameof(options.AuthenticationData)); + } + + if (options.AuthenticationMethod?.Any() == true) + { + Throw(nameof(options.AuthenticationMethod)); + } + + // Will relevant properties. + + if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified) + { + Throw(nameof(options.WillPayloadFormatIndicator)); + } + + if (options.WillContentType?.Any() == true) + { + Throw(nameof(options.WillContentType)); + } + + if (options.WillCorrelationData?.Any() == true) + { + Throw(nameof(options.WillCorrelationData)); + } + + if (options.WillResponseTopic?.Any() == true) + { + Throw(nameof(options.WillResponseTopic)); + } + + if (options.WillDelayInterval > 0) + { + Throw(nameof(options.WillDelayInterval)); + } + + if (options.WillMessageExpiryInterval > 0) + { + Throw(nameof(options.WillMessageExpiryInterval)); + } + + if (options.WillUserProperties?.Any() == true) + { + Throw(nameof(options.WillUserProperties)); + } + } + + 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/Client/Options/MqttClientTcpOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs index 59483e896..7ef4cf38c 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs @@ -5,50 +5,49 @@ using System.Net; using System.Net.Sockets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientTcpOptions : IMqttClientChannelOptions { - public sealed class MqttClientTcpOptions : IMqttClientChannelOptions + public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified; + + public int BufferSize { get; set; } = 8192; + + /// + /// Gets or sets whether the underlying socket should run in dual mode. + /// Leaving this _null_ will avoid setting this value at socket level. + /// Setting this a value other than _null_ will throw an exception when only IPv4 is supported on the machine. + /// + public bool? DualMode { get; set; } + + public LingerOption LingerState { get; set; } = new(true, 0); + + /// + /// Gets the local endpoint (network card) which is used by the client. + /// Set it to _null_ to let the OS select the network card. + /// + public EndPoint LocalEndpoint { get; set; } + + /// + /// Enables or disables the Nagle algorithm for the socket. + /// This is only supported for TCP. + /// For other protocol types the value is ignored. + /// Default: true + /// + public bool NoDelay { get; set; } = true; + + /// + /// The MQTT transport is usually TCP but when using other endpoint types like + /// unix sockets it must be changed (IP for unix sockets). + /// + public ProtocolType ProtocolType { get; set; } = ProtocolType.Tcp; + + public EndPoint RemoteEndpoint { get; set; } + + public MqttClientTlsOptions TlsOptions { get; set; } = new(); + + public override string ToString() { - public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified; - - public int BufferSize { get; set; } = 8192; - - /// - /// Gets or sets whether the underlying socket should run in dual mode. - /// Leaving this _null_ will avoid setting this value at socket level. - /// Setting this a value other than _null_ will throw an exception when only IPv4 is supported on the machine. - /// - public bool? DualMode { get; set; } - - public LingerOption LingerState { get; set; } = new LingerOption(true, 0); - - /// - /// Gets the local endpoint (network card) which is used by the client. - /// Set it to _null_ to let the OS select the network card. - /// - public EndPoint LocalEndpoint { get; set; } - - /// - /// Enables or disables the Nagle algorithm for the socket. - /// This is only supported for TCP. - /// For other protocol types the value is ignored. - /// Default: true - /// - public bool NoDelay { get; set; } = true; - - /// - /// The MQTT transport is usually TCP but when using other endpoint types like - /// unix sockets it must be changed (IP for unix sockets). - /// - public ProtocolType ProtocolType { get; set; } = ProtocolType.Tcp; - - public EndPoint RemoteEndpoint { get; set; } - - public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); - - public override string ToString() - { - return RemoteEndpoint?.ToString() ?? string.Empty; - } + return RemoteEndpoint?.ToString() ?? string.Empty; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index 4598c008c..4df8c7a2e 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -8,50 +8,49 @@ using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientTlsOptions { - public sealed class MqttClientTlsOptions - { - public Func CertificateValidationHandler { get; set; } + public Func CertificateValidationHandler { get; set; } - public Func CertificateSelectionHandler { get; set; } + public Func CertificateSelectionHandler { get; set; } - public bool UseTls { get; set; } + public bool UseTls { get; set; } - public bool IgnoreCertificateRevocationErrors { get; set; } + public bool IgnoreCertificateRevocationErrors { get; set; } - public bool IgnoreCertificateChainErrors { get; set; } + public bool IgnoreCertificateChainErrors { get; set; } - public bool AllowUntrustedCertificates { get; set; } + public bool AllowUntrustedCertificates { get; set; } - public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online; + public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online; - /// - /// Gets or sets the provider for certificates. - /// This provider gets called whenever the client wants to connect - /// with the server and requires certificates for authentication. - /// The implementation may return different certificates each time. - /// - public IMqttClientCertificatesProvider ClientCertificatesProvider { get; set; } + /// + /// Gets or sets the provider for certificates. + /// This provider gets called whenever the client wants to connect + /// with the server and requires certificates for authentication. + /// The implementation may return different certificates each time. + /// + public IMqttClientCertificatesProvider ClientCertificatesProvider { get; set; } - public List ApplicationProtocols { get; set; } + public List ApplicationProtocols { get; set; } - public CipherSuitesPolicy CipherSuitesPolicy { get; set; } + public CipherSuitesPolicy CipherSuitesPolicy { get; set; } - public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption; + public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption; - public bool AllowRenegotiation { get; set; } = true; + public bool AllowRenegotiation { get; set; } = true; - /// - /// Gets or sets the target host. - /// If the value is null or empty the same host as the TCP socket host will be used. - /// - public string TargetHost { get; set; } + /// + /// Gets or sets the target host. + /// If the value is null or empty the same host as the TCP socket host will be used. + /// + public string TargetHost { get; set; } - public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; + public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; #if NET7_0_OR_GREATER public X509Certificate2Collection TrustChain { get; set; } #endif - } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs index 64d678c47..204e94b66 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptionsBuilder.cs @@ -4,141 +4,141 @@ using System; using System.Collections.Generic; +using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; -using System.Net.Security; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientTlsOptionsBuilder { - public sealed class MqttClientTlsOptionsBuilder + readonly MqttClientTlsOptions _tlsOptions = new() { - readonly MqttClientTlsOptions _tlsOptions = new MqttClientTlsOptions - { - // If someone used this builder the change is very very high that TLS - // should be actually used. - UseTls = true - }; + // If someone used this builder the change is very very high that TLS + // should be actually used. + UseTls = true + }; - public MqttClientTlsOptions Build() - { - return _tlsOptions; - } + public MqttClientTlsOptions Build() + { + return _tlsOptions; + } - public MqttClientTlsOptionsBuilder UseTls(bool useTls = true) - { - _tlsOptions.UseTls = useTls; - return this; - } + public MqttClientTlsOptionsBuilder UseTls(bool useTls = true) + { + _tlsOptions.UseTls = useTls; + return this; + } - public MqttClientTlsOptionsBuilder WithAllowUntrustedCertificates(bool allowUntrustedCertificates = true) - { - _tlsOptions.AllowUntrustedCertificates = allowUntrustedCertificates; - return this; - } + public MqttClientTlsOptionsBuilder WithAllowUntrustedCertificates(bool allowUntrustedCertificates = true) + { + _tlsOptions.AllowUntrustedCertificates = allowUntrustedCertificates; + return this; + } - public MqttClientTlsOptionsBuilder WithCertificateValidationHandler(Func certificateValidationHandler) + public MqttClientTlsOptionsBuilder WithCertificateValidationHandler(Func certificateValidationHandler) + { + if (certificateValidationHandler == null) { - if (certificateValidationHandler == null) - { - throw new ArgumentNullException(nameof(certificateValidationHandler)); - } - - _tlsOptions.CertificateValidationHandler = certificateValidationHandler; - return this; + throw new ArgumentNullException(nameof(certificateValidationHandler)); } - public MqttClientTlsOptionsBuilder WithCertificateSelectionHandler(Func certificateSelectionHandler) - { - if (certificateSelectionHandler == null) - { - throw new ArgumentNullException(nameof(certificateSelectionHandler)); - } - - _tlsOptions.CertificateSelectionHandler = certificateSelectionHandler; - return this; - } + _tlsOptions.CertificateValidationHandler = certificateValidationHandler; + return this; + } - public MqttClientTlsOptionsBuilder WithClientCertificates(IEnumerable certificates) + public MqttClientTlsOptionsBuilder WithCertificateSelectionHandler(Func certificateSelectionHandler) + { + if (certificateSelectionHandler == null) { - if (certificates == null) - { - throw new ArgumentNullException(nameof(certificates)); - } - - _tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates); - return this; + throw new ArgumentNullException(nameof(certificateSelectionHandler)); } - public MqttClientTlsOptionsBuilder WithClientCertificates(X509Certificate2Collection certificates) - { - if (certificates == null) - { - throw new ArgumentNullException(nameof(certificates)); - } - - _tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates); - return this; - } + _tlsOptions.CertificateSelectionHandler = certificateSelectionHandler; + return this; + } - public MqttClientTlsOptionsBuilder WithClientCertificatesProvider(IMqttClientCertificatesProvider clientCertificatesProvider) + public MqttClientTlsOptionsBuilder WithClientCertificates(IEnumerable certificates) + { + if (certificates == null) { - _tlsOptions.ClientCertificatesProvider = clientCertificatesProvider; - return this; + throw new ArgumentNullException(nameof(certificates)); } - public MqttClientTlsOptionsBuilder WithIgnoreCertificateChainErrors(bool ignoreCertificateChainErrors = true) - { - _tlsOptions.IgnoreCertificateChainErrors = ignoreCertificateChainErrors; - return this; - } + _tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates); + return this; + } - public MqttClientTlsOptionsBuilder WithIgnoreCertificateRevocationErrors(bool ignoreCertificateRevocationErrors = true) + public MqttClientTlsOptionsBuilder WithClientCertificates(X509Certificate2Collection certificates) + { + if (certificates == null) { - _tlsOptions.IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors; - return this; + throw new ArgumentNullException(nameof(certificates)); } - public MqttClientTlsOptionsBuilder WithRevocationMode(X509RevocationMode revocationMode) - { - _tlsOptions.RevocationMode = revocationMode; - return this; - } + _tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates); + return this; + } - public MqttClientTlsOptionsBuilder WithSslProtocols(SslProtocols sslProtocols) - { - _tlsOptions.SslProtocol = sslProtocols; - return this; - } + public MqttClientTlsOptionsBuilder WithClientCertificatesProvider(IMqttClientCertificatesProvider clientCertificatesProvider) + { + _tlsOptions.ClientCertificatesProvider = clientCertificatesProvider; + return this; + } - public MqttClientTlsOptionsBuilder WithTargetHost(string targetHost) - { - _tlsOptions.TargetHost = targetHost; - return this; - } + public MqttClientTlsOptionsBuilder WithIgnoreCertificateChainErrors(bool ignoreCertificateChainErrors = true) + { + _tlsOptions.IgnoreCertificateChainErrors = ignoreCertificateChainErrors; + return this; + } - public MqttClientTlsOptionsBuilder WithAllowRenegotiation(bool allowRenegotiation = true) - { - _tlsOptions.AllowRenegotiation = allowRenegotiation; - return this; - } + public MqttClientTlsOptionsBuilder WithIgnoreCertificateRevocationErrors(bool ignoreCertificateRevocationErrors = true) + { + _tlsOptions.IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors; + return this; + } - public MqttClientTlsOptionsBuilder WithApplicationProtocols(List applicationProtocols) - { - _tlsOptions.ApplicationProtocols = applicationProtocols; - return this; - } + public MqttClientTlsOptionsBuilder WithRevocationMode(X509RevocationMode revocationMode) + { + _tlsOptions.RevocationMode = revocationMode; + return this; + } - public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(CipherSuitesPolicy cipherSuitePolicy) - { - _tlsOptions.CipherSuitesPolicy = cipherSuitePolicy; - return this; - } + public MqttClientTlsOptionsBuilder WithSslProtocols(SslProtocols sslProtocols) + { + _tlsOptions.SslProtocol = sslProtocols; + return this; + } - public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(EncryptionPolicy encryptionPolicy) - { - _tlsOptions.EncryptionPolicy = encryptionPolicy; - return this; - } + public MqttClientTlsOptionsBuilder WithTargetHost(string targetHost) + { + _tlsOptions.TargetHost = targetHost; + return this; + } + + public MqttClientTlsOptionsBuilder WithAllowRenegotiation(bool allowRenegotiation = true) + { + _tlsOptions.AllowRenegotiation = allowRenegotiation; + return this; + } + + public MqttClientTlsOptionsBuilder WithApplicationProtocols(List applicationProtocols) + { + _tlsOptions.ApplicationProtocols = applicationProtocols; + return this; + } + + public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(CipherSuitesPolicy cipherSuitePolicy) + { + _tlsOptions.CipherSuitesPolicy = cipherSuitePolicy; + return this; + } + + public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(EncryptionPolicy encryptionPolicy) + { + _tlsOptions.EncryptionPolicy = encryptionPolicy; + return this; + } #if NET7_0_OR_GREATER public MqttClientTlsOptionsBuilder WithTrustChain(X509Certificate2Collection chain) @@ -147,5 +147,4 @@ public MqttClientTlsOptionsBuilder WithTrustChain(X509Certificate2Collection cha return this; } #endif - } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs index dd6fd0bf4..3db95c027 100644 --- a/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs @@ -7,39 +7,38 @@ using System.Net; using System.Net.WebSockets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions { - public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions - { - public CookieContainer CookieContainer { get; set; } + public CookieContainer CookieContainer { get; set; } - public ICredentials Credentials { get; set; } + public ICredentials Credentials { get; set; } - public MqttClientWebSocketProxyOptions ProxyOptions { get; set; } + /// + /// Gets or sets the keep alive interval for the Web Socket connection. + /// This is not related to the keep alive interval for the MQTT protocol. + /// + public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval; - public IDictionary RequestHeaders { get; set; } + public MqttClientWebSocketProxyOptions ProxyOptions { get; set; } - public ICollection SubProtocols { get; set; } = new List { "mqtt" }; + public IDictionary RequestHeaders { get; set; } - public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); + public ICollection SubProtocols { get; set; } = new List { "mqtt" }; - public string Uri { get; set; } + public MqttClientTlsOptions TlsOptions { get; set; } = new(); - public override string ToString() - { - return Uri; - } + public string Uri { get; set; } - /// - /// Gets or sets the keep alive interval for the Web Socket connection. - /// This is not related to the keep alive interval for the MQTT protocol. - /// - public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval; + /// + /// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection. + /// This is not related to the credentials which are used for the MQTT protocol. + /// + public bool UseDefaultCredentials { get; set; } - /// - /// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection. - /// This is not related to the credentials which are used for the MQTT protocol. - /// - public bool UseDefaultCredentials { get; set; } + public override string ToString() + { + return Uri; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientWebSocketOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptionsBuilder.cs index 245e4510f..1981dd1d0 100644 --- a/Source/MQTTnet/Client/Options/MqttClientWebSocketOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptionsBuilder.cs @@ -6,77 +6,76 @@ using System.Collections.Generic; using System.Net; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientWebSocketOptionsBuilder { - public sealed class MqttClientWebSocketOptionsBuilder + readonly MqttClientWebSocketOptions _webSocketOptions = new(); + + public MqttClientWebSocketOptions Build() { - readonly MqttClientWebSocketOptions _webSocketOptions = new MqttClientWebSocketOptions(); + return _webSocketOptions; + } - public MqttClientWebSocketOptions Build() - { - return _webSocketOptions; - } + public MqttClientWebSocketOptionsBuilder WithCookieContainer(CookieContainer cookieContainer) + { + _webSocketOptions.CookieContainer = cookieContainer; + return this; + } - public MqttClientWebSocketOptionsBuilder WithCookieContainer(CookieContainer cookieContainer) - { - _webSocketOptions.CookieContainer = cookieContainer; - return this; - } + public MqttClientWebSocketOptionsBuilder WithCookieContainer(ICredentials credentials) + { + _webSocketOptions.Credentials = credentials; + return this; + } - public MqttClientWebSocketOptionsBuilder WithCookieContainer(ICredentials credentials) - { - _webSocketOptions.Credentials = credentials; - return this; - } + public MqttClientWebSocketOptionsBuilder WithKeepAliveInterval(TimeSpan keepAliveInterval) + { + _webSocketOptions.KeepAliveInterval = keepAliveInterval; + return this; + } - public MqttClientWebSocketOptionsBuilder WithProxyOptions(MqttClientWebSocketProxyOptions proxyOptions) - { - _webSocketOptions.ProxyOptions = proxyOptions; - return this; - } + public MqttClientWebSocketOptionsBuilder WithProxyOptions(MqttClientWebSocketProxyOptions proxyOptions) + { + _webSocketOptions.ProxyOptions = proxyOptions; + return this; + } - public MqttClientWebSocketOptionsBuilder WithProxyOptions(Action configure) + public MqttClientWebSocketOptionsBuilder WithProxyOptions(Action configure) + { + if (configure == null) { - if (configure == null) - { - throw new ArgumentNullException(nameof(configure)); - } - - var proxyOptionsBuilder = new MqttClientWebSocketProxyOptionsBuilder(); - configure.Invoke(proxyOptionsBuilder); - - _webSocketOptions.ProxyOptions = proxyOptionsBuilder.Build(); - return this; + throw new ArgumentNullException(nameof(configure)); } - public MqttClientWebSocketOptionsBuilder WithRequestHeaders(IDictionary requestHeaders) - { - _webSocketOptions.RequestHeaders = requestHeaders; - return this; - } + var proxyOptionsBuilder = new MqttClientWebSocketProxyOptionsBuilder(); + configure.Invoke(proxyOptionsBuilder); - public MqttClientWebSocketOptionsBuilder WithSubProtocols(ICollection subProtocols) - { - _webSocketOptions.SubProtocols = subProtocols; - return this; - } + _webSocketOptions.ProxyOptions = proxyOptionsBuilder.Build(); + return this; + } - public MqttClientWebSocketOptionsBuilder WithUri(string uri) - { - _webSocketOptions.Uri = uri; - return this; - } + public MqttClientWebSocketOptionsBuilder WithRequestHeaders(IDictionary requestHeaders) + { + _webSocketOptions.RequestHeaders = requestHeaders; + return this; + } - public MqttClientWebSocketOptionsBuilder WithKeepAliveInterval(TimeSpan keepAliveInterval) - { - _webSocketOptions.KeepAliveInterval = keepAliveInterval; - return this; - } + public MqttClientWebSocketOptionsBuilder WithSubProtocols(ICollection subProtocols) + { + _webSocketOptions.SubProtocols = subProtocols; + return this; + } - public MqttClientWebSocketOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true) - { - _webSocketOptions.UseDefaultCredentials = useDefaultCredentials; - return this; - } + public MqttClientWebSocketOptionsBuilder WithUri(string uri) + { + _webSocketOptions.Uri = uri; + return this; + } + + public MqttClientWebSocketOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true) + { + _webSocketOptions.UseDefaultCredentials = useDefaultCredentials; + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs index 3381109ef..9286ac957 100644 --- a/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs @@ -2,22 +2,21 @@ // 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.Client +namespace MQTTnet.Client; + +public sealed class MqttClientWebSocketProxyOptions { - public sealed class MqttClientWebSocketProxyOptions - { - public string Address { get; set; } + public string Address { get; set; } - public string Username { get; set; } + public string[] BypassList { get; set; } - public string Password { get; set; } + public bool BypassOnLocal { get; set; } - public string Domain { get; set; } + public string Domain { get; set; } - public bool BypassOnLocal { get; set; } + public string Password { get; set; } - public bool UseDefaultCredentials { get; set; } + public bool UseDefaultCredentials { get; set; } - public string[] BypassList { get; set; } - } + public string Username { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptionsBuilder.cs index 3fa736a97..45f35e8c5 100644 --- a/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptionsBuilder.cs @@ -5,63 +5,62 @@ using System.Collections.Generic; using System.Linq; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientWebSocketProxyOptionsBuilder { - public sealed class MqttClientWebSocketProxyOptionsBuilder + readonly MqttClientWebSocketProxyOptions _proxyOptions = new(); + + public MqttClientWebSocketProxyOptions Build() { - readonly MqttClientWebSocketProxyOptions _proxyOptions = new MqttClientWebSocketProxyOptions(); + return _proxyOptions; + } - public MqttClientWebSocketProxyOptionsBuilder WithAddress(string address) - { - _proxyOptions.Address = address; - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithUsername(string username) - { - _proxyOptions.Username = username; - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithPassword(string password) - { - _proxyOptions.Password = password; - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithDomain(string domain) - { - _proxyOptions.Domain = domain; - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithBypassOnLocal(bool bypassOnLocal = true) - { - _proxyOptions.BypassOnLocal = bypassOnLocal; - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithBypassList(string[] bypassList) - { - _proxyOptions.BypassList = bypassList; - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithBypassList(IEnumerable bypassList) - { - _proxyOptions.BypassList = bypassList?.ToArray(); - return this; - } - - public MqttClientWebSocketProxyOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true) - { - _proxyOptions.UseDefaultCredentials = useDefaultCredentials; - return this; - } - - public MqttClientWebSocketProxyOptions Build() - { - return _proxyOptions; - } + public MqttClientWebSocketProxyOptionsBuilder WithAddress(string address) + { + _proxyOptions.Address = address; + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithBypassList(string[] bypassList) + { + _proxyOptions.BypassList = bypassList; + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithBypassList(IEnumerable bypassList) + { + _proxyOptions.BypassList = bypassList?.ToArray(); + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithBypassOnLocal(bool bypassOnLocal = true) + { + _proxyOptions.BypassOnLocal = bypassOnLocal; + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithDomain(string domain) + { + _proxyOptions.Domain = domain; + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithPassword(string password) + { + _proxyOptions.Password = password; + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true) + { + _proxyOptions.UseDefaultCredentials = useDefaultCredentials; + return this; + } + + public MqttClientWebSocketProxyOptionsBuilder WithUsername(string username) + { + _proxyOptions.Username = username; + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs index ee9f6c73f..6ade0b558 100644 --- a/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs +++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs @@ -2,19 +2,18 @@ // 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.Client +namespace MQTTnet.Client; + +public enum MqttClientPublishReasonCode { - public enum MqttClientPublishReasonCode - { - 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/Client/Publishing/MqttClientPublishResult.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs index e36ce75e4..5653d69f7 100644 --- a/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs +++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs @@ -5,52 +5,51 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientPublishResult { - public sealed class MqttClientPublishResult + public MqttClientPublishResult(ushort? packetIdentifier, MqttClientPublishReasonCode reasonCode, string reasonString, IReadOnlyCollection userProperties) { - public MqttClientPublishResult(ushort? packetIdentifier, MqttClientPublishReasonCode reasonCode, string reasonString, IReadOnlyCollection userProperties) - { - PacketIdentifier = packetIdentifier; - ReasonCode = reasonCode; - ReasonString = reasonString; - UserProperties = userProperties; - } - - /// - /// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or - /// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the - /// topic but overall transmit - /// to the server etc. was a success. - /// - public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers; - - /// - /// Gets the packet identifier which was used for this publish. - /// - public ushort? PacketIdentifier { get; } - - /// - /// Gets or sets the reason code. - /// MQTT 5.0.0+ feature. - /// - public MqttClientPublishReasonCode ReasonCode { get; } - - /// - /// Gets or sets the reason string. - /// MQTT 5.0.0+ feature. - /// - public string ReasonString { get; } - - /// - /// 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. - /// MQTT 5.0.0+ feature. - /// - public IReadOnlyCollection UserProperties { get; } + PacketIdentifier = packetIdentifier; + ReasonCode = reasonCode; + ReasonString = reasonString; + UserProperties = userProperties; } + + /// + /// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or + /// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the + /// topic but overall transmit + /// to the server etc. was a success. + /// + public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers; + + /// + /// Gets the packet identifier which was used for this publish. + /// + public ushort? PacketIdentifier { get; } + + /// + /// Gets or sets the reason code. + /// MQTT 5.0.0+ feature. + /// + public MqttClientPublishReasonCode ReasonCode { get; } + + /// + /// Gets or sets the reason string. + /// MQTT 5.0.0+ feature. + /// + public string ReasonString { get; } + + /// + /// 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. + /// MQTT 5.0.0+ feature. + /// + public IReadOnlyCollection UserProperties { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Publishing/MqttClientPublishResultFactory.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishResultFactory.cs index 3f4c63977..4792b9170 100644 --- a/Source/MQTTnet/Client/Publishing/MqttClientPublishResultFactory.cs +++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishResultFactory.cs @@ -6,57 +6,56 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientPublishResultFactory { - public sealed class MqttClientPublishResultFactory + static readonly IReadOnlyCollection EmptyUserProperties = new List(); + static readonly MqttClientPublishResult AtMostOnceSuccessResult = new(null, MqttClientPublishReasonCode.Success, null, EmptyUserProperties); + + public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket) + { + // QoS 0 has no response. So we treat it as a success always. + if (pubAckPacket == null) + { + return AtMostOnceSuccessResult; + } + + var result = new MqttClientPublishResult( + pubAckPacket.PacketIdentifier, + // Both enums have the same values. So it can be easily converted. + (MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode, + pubAckPacket.ReasonString, + pubAckPacket.UserProperties ?? EmptyUserProperties); + + return result; + } + + public MqttClientPublishResult Create(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket) { - static readonly IReadOnlyCollection EmptyUserProperties = new List(); - static readonly MqttClientPublishResult AtMostOnceSuccessResult = new MqttClientPublishResult(null, MqttClientPublishReasonCode.Success, null, EmptyUserProperties); + if (pubRecPacket == null || pubCompPacket == null) + { + var packetIdentifier = pubRecPacket?.PacketIdentifier ?? pubCompPacket?.PacketIdentifier; + return new MqttClientPublishResult(packetIdentifier, MqttClientPublishReasonCode.ImplementationSpecificError, null, EmptyUserProperties); + } - public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket) + // The PUBCOMP is the last packet in QoS 2. So we use the results from that instead of PUBREC. + if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound) { - // QoS 0 has no response. So we treat it as a success always. - if (pubAckPacket == null) - { - return AtMostOnceSuccessResult; - } - - var result = new MqttClientPublishResult( - pubAckPacket.PacketIdentifier, - // Both enums have the same values. So it can be easily converted. - (MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode, - pubAckPacket.ReasonString, - pubAckPacket.UserProperties ?? EmptyUserProperties); - - return result; + return new MqttClientPublishResult( + pubCompPacket.PacketIdentifier, + MqttClientPublishReasonCode.UnspecifiedError, + pubCompPacket.ReasonString, + pubCompPacket.UserProperties ?? EmptyUserProperties); } - public MqttClientPublishResult Create(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket) + var reasonCode = MqttClientPublishReasonCode.Success; + if (pubRecPacket.ReasonCode != MqttPubRecReasonCode.Success) { - if (pubRecPacket == null || pubCompPacket == null) - { - var packetIdentifier = pubRecPacket?.PacketIdentifier ?? pubCompPacket?.PacketIdentifier; - return new MqttClientPublishResult(packetIdentifier, MqttClientPublishReasonCode.ImplementationSpecificError, null, EmptyUserProperties); - } - - // The PUBCOMP is the last packet in QoS 2. So we use the results from that instead of PUBREC. - if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound) - { - return new MqttClientPublishResult( - pubCompPacket.PacketIdentifier, - MqttClientPublishReasonCode.UnspecifiedError, - pubCompPacket.ReasonString, - pubCompPacket.UserProperties ?? EmptyUserProperties); - } - - var reasonCode = MqttClientPublishReasonCode.Success; - if (pubRecPacket.ReasonCode != MqttPubRecReasonCode.Success) - { - // Both enums share the same values. - reasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode; - } - - return new MqttClientPublishResult(pubCompPacket.PacketIdentifier, reasonCode, null, pubCompPacket.UserProperties ?? EmptyUserProperties); + // Both enums share the same values. + reasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode; } + + return new MqttClientPublishResult(pubCompPacket.PacketIdentifier, reasonCode, null, pubCompPacket.UserProperties ?? EmptyUserProperties); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs index 3a6991976..fc4e43bf1 100644 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs @@ -8,88 +8,87 @@ using System.Threading.Tasks; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs { - public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs - { - readonly Func _acknowledgeHandler; + readonly Func _acknowledgeHandler; - int _isAcknowledged; + int _isAcknowledged; - public MqttApplicationMessageReceivedEventArgs( - string clientId, - MqttApplicationMessage applicationMessage, - MqttPublishPacket publishPacket, - Func acknowledgeHandler) - { - ClientId = clientId; - ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); - PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); - _acknowledgeHandler = acknowledgeHandler; - } + public MqttApplicationMessageReceivedEventArgs( + string clientId, + MqttApplicationMessage applicationMessage, + MqttPublishPacket publishPacket, + Func acknowledgeHandler) + { + ClientId = clientId; + ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); + PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); + _acknowledgeHandler = acknowledgeHandler; + } - public MqttApplicationMessage ApplicationMessage { get; } + public MqttApplicationMessage ApplicationMessage { get; } - /// - /// Gets or sets whether the library should send MQTT ACK packets automatically if required. - /// - public bool AutoAcknowledge { get; set; } = true; + /// + /// Gets or sets whether the library should send MQTT ACK packets automatically if required. + /// + public bool AutoAcknowledge { get; set; } = true; - /// - /// 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 or sets whether this message was handled. - /// This value can be used in user code for custom control flow. - /// - public bool IsHandled { get; set; } + /// + /// Gets or sets whether this message was handled. + /// This value can be used in user code for custom control flow. + /// + public bool IsHandled { get; set; } - /// - /// Gets the identifier of the MQTT packet - /// - public ushort PacketIdentifier => PublishPacket.PacketIdentifier; + /// + /// Gets the identifier of the MQTT packet + /// + public ushort PacketIdentifier => PublishPacket.PacketIdentifier; - /// - /// Indicates if the processing of this PUBLISH packet has failed. - /// If the processing has failed the client will not send an ACK packet etc. - /// - public bool ProcessingFailed { get; set; } + /// + /// Indicates if the processing of this PUBLISH packet has failed. + /// If the processing has failed the client will not send an ACK packet etc. + /// + public bool ProcessingFailed { get; set; } - /// - /// Gets or sets the reason code which will be sent to the server. - /// - public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success; + /// + /// Gets or sets the reason code which will be sent to the server. + /// + public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success; - /// - /// Gets or sets the reason string which will be sent to the server in the ACK packet. - /// - public string ResponseReasonString { get; set; } + /// + /// Gets or sets the reason string which will be sent to the server in the ACK packet. + /// + public string ResponseReasonString { get; set; } - /// - /// Gets or sets the user properties which will be sent to the server in the ACK packet etc. - /// - public List ResponseUserProperties { get; } = new List(); + /// + /// Gets or sets the user properties which will be sent to the server in the ACK packet etc. + /// + public List ResponseUserProperties { get; } = new(); - public object Tag { get; set; } + public object Tag { get; set; } - internal MqttPublishPacket PublishPacket { get; set; } + internal MqttPublishPacket PublishPacket { get; set; } - public Task AcknowledgeAsync(CancellationToken cancellationToken) + public Task AcknowledgeAsync(CancellationToken cancellationToken) + { + if (_acknowledgeHandler == null) { - if (_acknowledgeHandler == null) - { - throw new NotSupportedException("Deferred acknowledgement of application message is not yet supported in MQTTnet server."); - } - - if (Interlocked.CompareExchange(ref _isAcknowledged, 1, 0) == 0) - { - return _acknowledgeHandler(this, cancellationToken); - } + throw new NotSupportedException("Deferred acknowledgement of application message is not yet supported in MQTTnet server."); + } - throw new InvalidOperationException("The application message is already acknowledged."); + if (Interlocked.CompareExchange(ref _isAcknowledged, 1, 0) == 0) + { + return _acknowledgeHandler(this, cancellationToken); } + + throw new InvalidOperationException("The application message is already acknowledged."); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedReasonCode.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedReasonCode.cs index 1a00bbd4b..95918a09e 100644 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedReasonCode.cs +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedReasonCode.cs @@ -2,19 +2,18 @@ // 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.Client +namespace MQTTnet.Client; + +public enum MqttApplicationMessageReceivedReasonCode { - public enum MqttApplicationMessageReceivedReasonCode - { - Success = 0, - NoMatchingSubscribers = 16, - UnspecifiedError = 128, - ImplementationSpecificError = 131, - NotAuthorized = 135, - TopicNameInvalid = 144, - PacketIdentifierInUse = 145, - PacketIdentifierNotFound = 146, - QuotaExceeded = 151, - PayloadFormatInvalid = 153 - } + Success = 0, + NoMatchingSubscribers = 16, + UnspecifiedError = 128, + ImplementationSpecificError = 131, + NotAuthorized = 135, + TopicNameInvalid = 144, + PacketIdentifierInUse = 145, + PacketIdentifierNotFound = 146, + QuotaExceeded = 151, + PayloadFormatInvalid = 153 } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs index 47715a1bd..f222c47aa 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs @@ -5,36 +5,35 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientSubscribeOptions { - public sealed class MqttClientSubscribeOptions - { - /// - /// Gets or sets the subscription identifier. - /// 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. - /// MQTT 5.0.0+ feature. - /// - public uint SubscriptionIdentifier { get; set; } + /// + /// Gets or sets the subscription identifier. + /// 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. + /// MQTT 5.0.0+ feature. + /// + public uint SubscriptionIdentifier { get; set; } - /// - /// 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 List(); + /// + /// 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(); - /// - /// 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. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties { 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. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs index 7075ef89c..4ea39888c 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs @@ -8,100 +8,99 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientSubscribeOptionsBuilder { - public sealed class MqttClientSubscribeOptionsBuilder + readonly MqttClientSubscribeOptions _subscribeOptions = new(); + + public MqttClientSubscribeOptions Build() { - readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions(); + return _subscribeOptions; + } - public MqttClientSubscribeOptions Build() + public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) + { + if (subscriptionIdentifier == 0) { - return _subscribeOptions; + throw new MqttProtocolViolationException("Subscription identifier cannot be 0."); } - public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) - { - if (subscriptionIdentifier == 0) - { - throw new MqttProtocolViolationException("Subscription identifier cannot be 0."); - } + _subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier; + return this; + } - _subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier; - return this; - } + public MqttClientSubscribeOptionsBuilder WithTopicFilter( + string topic, + MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, + bool noLocal = false, + bool retainAsPublished = false, + MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe) + { + return WithTopicFilter( + new MqttTopicFilter + { + Topic = topic, + QualityOfServiceLevel = qualityOfServiceLevel, + NoLocal = noLocal, + RetainAsPublished = retainAsPublished, + RetainHandling = retainHandling + }); + } - public MqttClientSubscribeOptionsBuilder WithTopicFilter( - string topic, - MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, - bool noLocal = false, - bool retainAsPublished = false, - MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe) + public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action topicFilterBuilder) + { + if (topicFilterBuilder == null) { - return WithTopicFilter( - new MqttTopicFilter - { - Topic = topic, - QualityOfServiceLevel = qualityOfServiceLevel, - NoLocal = noLocal, - RetainAsPublished = retainAsPublished, - RetainHandling = retainHandling - }); + throw new ArgumentNullException(nameof(topicFilterBuilder)); } - public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action topicFilterBuilder) - { - if (topicFilterBuilder == null) - { - throw new ArgumentNullException(nameof(topicFilterBuilder)); - } + var internalTopicFilterBuilder = new MqttTopicFilterBuilder(); + topicFilterBuilder(internalTopicFilterBuilder); - var internalTopicFilterBuilder = new MqttTopicFilterBuilder(); - topicFilterBuilder(internalTopicFilterBuilder); + return WithTopicFilter(internalTopicFilterBuilder); + } - return WithTopicFilter(internalTopicFilterBuilder); + public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilterBuilder topicFilterBuilder) + { + if (topicFilterBuilder == null) + { + throw new ArgumentNullException(nameof(topicFilterBuilder)); } - public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilterBuilder topicFilterBuilder) - { - if (topicFilterBuilder == null) - { - throw new ArgumentNullException(nameof(topicFilterBuilder)); - } + return WithTopicFilter(topicFilterBuilder.Build()); + } - return WithTopicFilter(topicFilterBuilder.Build()); + public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter) + { + if (topicFilter == null) + { + throw new ArgumentNullException(nameof(topicFilter)); } - public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter) + if (_subscribeOptions.TopicFilters == null) { - if (topicFilter == null) - { - throw new ArgumentNullException(nameof(topicFilter)); - } - - if (_subscribeOptions.TopicFilters == null) - { - _subscribeOptions.TopicFilters = new List(); - } + _subscribeOptions.TopicFilters = new List(); + } - _subscribeOptions.TopicFilters.Add(topicFilter); + _subscribeOptions.TopicFilters.Add(topicFilter); - return this; - } + return this; + } - /// - /// Adds the user property to the subscribe options. - /// MQTT 5.0.0+ feature. - /// - public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value) + /// + /// Adds the user property to the subscribe options. + /// MQTT 5.0.0+ feature. + /// + public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value) + { + if (_subscribeOptions.UserProperties == null) { - if (_subscribeOptions.UserProperties == null) - { - _subscribeOptions.UserProperties = new List(); - } + _subscribeOptions.UserProperties = new List(); + } - _subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value)); + _subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value)); - return this; - } + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs index a28f6881d..c44b54dbc 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs @@ -7,52 +7,51 @@ using MQTTnet.Formatter; using MQTTnet.Protocol; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public static class MqttClientSubscribeOptionsValidator { - public static class MqttClientSubscribeOptionsValidator + public static void ThrowIfNotSupported(MqttClientSubscribeOptions options, MqttProtocolVersion protocolVersion) { - public static void ThrowIfNotSupported(MqttClientSubscribeOptions options, MqttProtocolVersion protocolVersion) + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (protocolVersion == MqttProtocolVersion.V500) + { + // Everything is supported. + return; + } + + if (options.UserProperties?.Any() == true) + { + Throw(nameof(options.UserProperties)); + } + + if (options.SubscriptionIdentifier != 0) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (protocolVersion == MqttProtocolVersion.V500) - { - // Everything is supported. - return; - } - - if (options.UserProperties?.Any() == true) - { - Throw(nameof(options.UserProperties)); - } - - if (options.SubscriptionIdentifier != 0) - { - Throw(nameof(options.SubscriptionIdentifier)); - } - - if (options.TopicFilters?.Any(t => t.NoLocal) == true) - { - Throw("NoLocal"); - } - - if (options.TopicFilters?.Any(t => t.RetainAsPublished) == true) - { - Throw("RetainAsPublished"); - } - - if (options.TopicFilters?.Any(t => t.RetainHandling != MqttRetainHandling.SendAtSubscribe) == true) - { - Throw("RetainHandling"); - } + Throw(nameof(options.SubscriptionIdentifier)); } - - static void Throw(string featureName) + + if (options.TopicFilters?.Any(t => t.NoLocal) == true) + { + Throw("NoLocal"); + } + + if (options.TopicFilters?.Any(t => t.RetainAsPublished) == true) + { + Throw("RetainAsPublished"); + } + + if (options.TopicFilters?.Any(t => t.RetainHandling != MqttRetainHandling.SendAtSubscribe) == true) { - throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0."); + Throw("RetainHandling"); } } + + 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/Client/Subscribing/MqttClientSubscribeResult.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs index 9459679b3..44b9b8312 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs @@ -6,38 +6,41 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientSubscribeResult { - public sealed class MqttClientSubscribeResult + public MqttClientSubscribeResult( + ushort packetIdentifier, + IReadOnlyCollection items, + string reasonString, + IReadOnlyCollection userProperties) { - public MqttClientSubscribeResult(ushort packetIdentifier, IReadOnlyCollection items, string reasonString, IReadOnlyCollection userProperties) - { - PacketIdentifier = packetIdentifier; - Items = items ?? throw new ArgumentNullException(nameof(items)); - ReasonString = reasonString; - UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties)); - } + PacketIdentifier = packetIdentifier; + Items = items ?? throw new ArgumentNullException(nameof(items)); + ReasonString = reasonString; + UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties)); + } - /// - /// Gets the result for every topic filter item. - /// - public IReadOnlyCollection Items { get; } - - /// - /// Gets the user properties which were part of the SUBACK packet. - /// MQTT 5.0.0+ feature. - /// - public IReadOnlyCollection UserProperties { get; } - - /// - /// Gets the reason string. - /// MQTT 5.0.0+ feature. - /// - public string ReasonString { get; } + /// + /// Gets the result for every topic filter item. + /// + public IReadOnlyCollection Items { get; } - /// - /// Gets the packet identifier which was used. - /// - public ushort PacketIdentifier { get; } - } + /// + /// Gets the packet identifier which was used. + /// + public ushort PacketIdentifier { get; } + + /// + /// Gets the reason string. + /// MQTT 5.0.0+ feature. + /// + public string ReasonString { get; } + + /// + /// Gets the user properties which were part of the SUBACK packet. + /// MQTT 5.0.0+ feature. + /// + public IReadOnlyCollection UserProperties { get; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs index 2213f4198..a001be1df 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs @@ -2,22 +2,21 @@ // 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.Client +namespace MQTTnet.Client; + +public enum MqttClientSubscribeResultCode { - public enum MqttClientSubscribeResultCode - { - GrantedQoS0 = 0x00, - GrantedQoS1 = 0x01, - GrantedQoS2 = 0x02, - UnspecifiedError = 0x80, - - ImplementationSpecificError = 131, - NotAuthorized = 135, - TopicFilterInvalid = 143, - PacketIdentifierInUse = 145, - QuotaExceeded = 151, - SharedSubscriptionsNotSupported = 158, - SubscriptionIdentifiersNotSupported = 161, - WildcardSubscriptionsNotSupported = 162 - } -} + GrantedQoS0 = 0x00, + GrantedQoS1 = 0x01, + GrantedQoS2 = 0x02, + UnspecifiedError = 0x80, + + 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/Client/Subscribing/MqttClientSubscribeResultFactory.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultFactory.cs index 5bb129b44..527d890b4 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultFactory.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultFactory.cs @@ -8,43 +8,42 @@ using MQTTnet.Exceptions; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientSubscribeResultFactory { - public sealed class MqttClientSubscribeResultFactory + static readonly IReadOnlyCollection EmptyUserProperties = new List(); + + public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) { - static readonly IReadOnlyCollection EmptyUserProperties = new List(); + if (subscribePacket == null) + { + throw new ArgumentNullException(nameof(subscribePacket)); + } - public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) + if (subAckPacket == null) { - if (subscribePacket == null) - { - throw new ArgumentNullException(nameof(subscribePacket)); - } - - if (subAckPacket == null) - { - throw new ArgumentNullException(nameof(subAckPacket)); - } - - // MQTTv5.0.0 handling. - if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count) - { - throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1]."); - } - - var items = new List(); - for (var i = 0; i < subscribePacket.TopicFilters.Count; i++) - { - items.Add(CreateSubscribeResultItem(i, subscribePacket, subAckPacket)); - } - - return new MqttClientSubscribeResult(subAckPacket.PacketIdentifier, items, subAckPacket.ReasonString, subAckPacket.UserProperties ?? EmptyUserProperties); + throw new ArgumentNullException(nameof(subAckPacket)); } - static MqttClientSubscribeResultItem CreateSubscribeResultItem(int index, MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) + // MQTTv5.0.0 handling. + if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count) { - var resultCode = (MqttClientSubscribeResultCode)subAckPacket.ReasonCodes[index]; - return new MqttClientSubscribeResultItem(subscribePacket.TopicFilters[index], resultCode); + throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1]."); } + + var items = new List(); + for (var i = 0; i < subscribePacket.TopicFilters.Count; i++) + { + items.Add(CreateSubscribeResultItem(i, subscribePacket, subAckPacket)); + } + + return new MqttClientSubscribeResult(subAckPacket.PacketIdentifier, items, subAckPacket.ReasonString, subAckPacket.UserProperties ?? EmptyUserProperties); + } + + static MqttClientSubscribeResultItem CreateSubscribeResultItem(int index, MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) + { + var resultCode = (MqttClientSubscribeResultCode)subAckPacket.ReasonCodes[index]; + return new MqttClientSubscribeResultItem(subscribePacket.TopicFilters[index], resultCode); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs index 620ccffed..a781ab280 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs @@ -5,26 +5,25 @@ using System; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientSubscribeResultItem { - public sealed class MqttClientSubscribeResultItem + public MqttClientSubscribeResultItem(MqttTopicFilter topicFilter, MqttClientSubscribeResultCode resultCode) { - public MqttClientSubscribeResultItem(MqttTopicFilter topicFilter, MqttClientSubscribeResultCode resultCode) - { - TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); - ResultCode = resultCode; - } + TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); + ResultCode = resultCode; + } - /// - /// Gets or sets the topic filter. - /// The topic filter can contain topics and wildcards. - /// - public MqttTopicFilter TopicFilter { get; } + /// + /// Gets or sets the result code. + /// MQTT 5.0.0+ feature. + /// + public MqttClientSubscribeResultCode ResultCode { get; } - /// - /// Gets or sets the result code. - /// MQTT 5.0.0+ feature. - /// - public MqttClientSubscribeResultCode ResultCode { get; } - } -} + /// + /// Gets or sets 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/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs index 239c1aaf6..7dd86cc8e 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs @@ -5,25 +5,24 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientUnsubscribeOptions { - 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 List(); + /// + /// 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(); - /// - /// 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. - /// MQTT 5.0.0+ feature. - /// - public List UserProperties { 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. + /// MQTT 5.0.0+ feature. + /// + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs index 1db311178..821f561c5 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs @@ -6,72 +6,71 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientUnsubscribeOptionsBuilder { - public sealed class MqttClientUnsubscribeOptionsBuilder + readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new(); + + public MqttClientUnsubscribeOptions Build() { - readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions(); + return _unsubscribeOptions; + } - public MqttClientUnsubscribeOptions Build() + public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic) + { + if (topic is null) { - return _unsubscribeOptions; + throw new ArgumentNullException(nameof(topic)); } - public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic) + if (_unsubscribeOptions.TopicFilters is null) { - if (topic is null) - { - throw new ArgumentNullException(nameof(topic)); - } + _unsubscribeOptions.TopicFilters = new List(); + } - if (_unsubscribeOptions.TopicFilters is null) - { - _unsubscribeOptions.TopicFilters = new List(); - } + _unsubscribeOptions.TopicFilters.Add(topic); - _unsubscribeOptions.TopicFilters.Add(topic); + return this; + } - return this; + public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter) + { + if (topicFilter is null) + { + throw new ArgumentNullException(nameof(topicFilter)); } - public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter) - { - if (topicFilter is null) - { - throw new ArgumentNullException(nameof(topicFilter)); - } + return WithTopicFilter(topicFilter.Topic); + } - return WithTopicFilter(topicFilter.Topic); - } + /// + /// Adds the user property to the unsubscribe options. + /// MQTT 5.0.0+ feature. + /// + public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value) + { + return WithUserProperty(new MqttUserProperty(name, value)); + } - /// - /// Adds the user property to the unsubscribe options. - /// MQTT 5.0.0+ feature. - /// - public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value) + /// + /// Adds the user property to the unsubscribe options. + /// MQTT 5.0.0+ feature. + /// + public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty) + { + if (userProperty is null) { - return WithUserProperty(new MqttUserProperty(name, value)); + throw new ArgumentNullException(nameof(userProperty)); } - /// - /// Adds the user property to the unsubscribe options. - /// MQTT 5.0.0+ feature. - /// - public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty) + if (_unsubscribeOptions.UserProperties is null) { - if (userProperty is null) - { - throw new ArgumentNullException(nameof(userProperty)); - } - - if (_unsubscribeOptions.UserProperties is null) - { - _unsubscribeOptions.UserProperties = new List(); - } + _unsubscribeOptions.UserProperties = new List(); + } - _unsubscribeOptions.UserProperties.Add(userProperty); + _unsubscribeOptions.UserProperties.Add(userProperty); - return this; - } + return this; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs index 0f9014279..0caab9e61 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs @@ -6,32 +6,31 @@ using System.Linq; using MQTTnet.Formatter; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public static class MqttClientUnsubscribeOptionsValidator { - public static class MqttClientUnsubscribeOptionsValidator + public static void ThrowIfNotSupported(MqttClientUnsubscribeOptions options, MqttProtocolVersion protocolVersion) { - public static void ThrowIfNotSupported(MqttClientUnsubscribeOptions options, MqttProtocolVersion protocolVersion) + if (options == null) { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (protocolVersion == MqttProtocolVersion.V500) - { - // Everything is supported. - return; - } + throw new ArgumentNullException(nameof(options)); + } - if (options.UserProperties?.Any() == true) - { - Throw(nameof(options.UserProperties)); - } + if (protocolVersion == MqttProtocolVersion.V500) + { + // Everything is supported. + return; } - static void Throw(string featureName) + if (options.UserProperties?.Any() == true) { - throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0."); + Throw(nameof(options.UserProperties)); } } + + 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/Client/Unsubscribing/MqttClientUnsubscribeResult.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs index 60aedcdff..93b548a32 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs @@ -6,42 +6,41 @@ using System.Collections.Generic; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientUnsubscribeResult { - public sealed class MqttClientUnsubscribeResult + public MqttClientUnsubscribeResult( + ushort packetIdentifier, + IReadOnlyCollection items, + string reasonString, + IReadOnlyCollection userProperties) { - public MqttClientUnsubscribeResult( - ushort packetIdentifier, - IReadOnlyCollection items, - string reasonString, - IReadOnlyCollection userProperties) - { - PacketIdentifier = packetIdentifier; - Items = items ?? throw new ArgumentNullException(nameof(items)); - ReasonString = reasonString; - UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties)); - } + PacketIdentifier = packetIdentifier; + Items = items ?? throw new ArgumentNullException(nameof(items)); + ReasonString = reasonString; + UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties)); + } - /// - /// Gets the result for every topic filter item. - /// - public IReadOnlyCollection Items { get; } + /// + /// Gets the result for every topic filter item. + /// + public IReadOnlyCollection Items { get; } - /// - /// Gets the packet identifier which was used. - /// - public ushort PacketIdentifier { get; } + /// + /// Gets the packet identifier which was used. + /// + public ushort PacketIdentifier { get; } - /// - /// Gets the reason string. - /// MQTT 5.0.0+ feature. - /// - public string ReasonString { get; } + /// + /// Gets the reason string. + /// MQTT 5.0.0+ feature. + /// + public string ReasonString { get; } - /// - /// Gets the user properties which were part of the UNSUBACK packet. - /// MQTT 5.0.0+ feature. - /// - public IReadOnlyCollection UserProperties { get; set; } - } + /// + /// Gets the user properties which were part of the UNSUBACK packet. + /// MQTT 5.0.0+ feature. + /// + public IReadOnlyCollection UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs index 521c88dc0..b401619ef 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.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.Client +namespace MQTTnet.Client; + +public enum MqttClientUnsubscribeResultCode { - public enum MqttClientUnsubscribeResultCode - { - 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/Client/Unsubscribing/MqttClientUnsubscribeResultFactory.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultFactory.cs index d204fd94e..060dd5e97 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultFactory.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultFactory.cs @@ -7,50 +7,49 @@ using MQTTnet.Exceptions; using MQTTnet.Packets; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientUnsubscribeResultFactory { - public sealed class MqttClientUnsubscribeResultFactory + static readonly IReadOnlyCollection EmptyUserProperties = new List(); + + public MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) { - static readonly IReadOnlyCollection EmptyUserProperties = new List(); + if (unsubscribePacket == null) + { + throw new ArgumentNullException(nameof(unsubscribePacket)); + } - public MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) + if (unsubAckPacket == null) { - if (unsubscribePacket == null) - { - throw new ArgumentNullException(nameof(unsubscribePacket)); - } - - if (unsubAckPacket == null) - { - throw new ArgumentNullException(nameof(unsubAckPacket)); - } - - // MQTTv3.1.1 has no reason code at all! - if (unsubAckPacket.ReasonCodes != null && unsubAckPacket.ReasonCodes.Count != unsubscribePacket.TopicFilters.Count) - { - throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); - } - - var items = new List(); - for (var i = 0; i < unsubscribePacket.TopicFilters.Count; i++) - { - items.Add(CreateUnsubscribeResultItem(i, unsubscribePacket, unsubAckPacket)); - } - - return new MqttClientUnsubscribeResult(unsubscribePacket.PacketIdentifier, items, unsubAckPacket.ReasonString, unsubAckPacket.UserProperties ?? EmptyUserProperties); + throw new ArgumentNullException(nameof(unsubAckPacket)); } - static MqttClientUnsubscribeResultItem CreateUnsubscribeResultItem(int index, MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) + // MQTTv3.1.1 has no reason code at all! + if (unsubAckPacket.ReasonCodes != null && unsubAckPacket.ReasonCodes.Count != unsubscribePacket.TopicFilters.Count) { - var resultCode = MqttClientUnsubscribeResultCode.Success; + throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); + } - if (unsubAckPacket.ReasonCodes != null) - { - // MQTTv3.1.1 has no reason code and no return code!. - resultCode = (MqttClientUnsubscribeResultCode)unsubAckPacket.ReasonCodes[index]; - } + var items = new List(); + for (var i = 0; i < unsubscribePacket.TopicFilters.Count; i++) + { + items.Add(CreateUnsubscribeResultItem(i, unsubscribePacket, unsubAckPacket)); + } - return new MqttClientUnsubscribeResultItem(unsubscribePacket.TopicFilters[index], resultCode); + return new MqttClientUnsubscribeResult(unsubscribePacket.PacketIdentifier, items, unsubAckPacket.ReasonString, unsubAckPacket.UserProperties ?? EmptyUserProperties); + } + + static MqttClientUnsubscribeResultItem CreateUnsubscribeResultItem(int index, MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) + { + var resultCode = MqttClientUnsubscribeResultCode.Success; + + if (unsubAckPacket.ReasonCodes != null) + { + // MQTTv3.1.1 has no reason code and no return code!. + resultCode = (MqttClientUnsubscribeResultCode)unsubAckPacket.ReasonCodes[index]; } + + return new MqttClientUnsubscribeResultItem(unsubscribePacket.TopicFilters[index], resultCode); } } \ No newline at end of file diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs index 997a2493a..289a83bcc 100644 --- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs +++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs @@ -4,26 +4,25 @@ using System; -namespace MQTTnet.Client +namespace MQTTnet.Client; + +public sealed class MqttClientUnsubscribeResultItem { - public sealed class MqttClientUnsubscribeResultItem + public MqttClientUnsubscribeResultItem(string topicFilter, MqttClientUnsubscribeResultCode resultCode) { - public MqttClientUnsubscribeResultItem(string topicFilter, MqttClientUnsubscribeResultCode resultCode) - { - TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); - ResultCode = resultCode; - } + TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); + ResultCode = resultCode; + } - /// - /// Gets or sets the result code. - /// MQTT 5.0.0+ feature. - /// - public MqttClientUnsubscribeResultCode ResultCode { get; } + /// + /// Gets or sets the result code. + /// MQTT 5.0.0+ feature. + /// + public MqttClientUnsubscribeResultCode ResultCode { 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/Formatter/MqttConnectPacketFactory.cs b/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs index 09096b689..5239fc2ad 100644 --- a/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs @@ -6,54 +6,53 @@ using MQTTnet.Client; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttConnectPacketFactory { - public sealed class MqttConnectPacketFactory + public static MqttConnectPacket Create(MqttClientOptions clientOptions) { - public MqttConnectPacket Create(MqttClientOptions clientOptions) + if (clientOptions == null) { - if (clientOptions == null) - { - throw new ArgumentNullException(nameof(clientOptions)); - } - - var connectPacket = new MqttConnectPacket - { - ClientId = clientOptions.ClientId, - Username = clientOptions.Credentials?.GetUserName(clientOptions), - Password = clientOptions.Credentials?.GetPassword(clientOptions), - CleanSession = clientOptions.CleanSession, - KeepAlivePeriod = (ushort)clientOptions.KeepAlivePeriod.TotalSeconds, - AuthenticationMethod = clientOptions.AuthenticationMethod, - AuthenticationData = clientOptions.AuthenticationData, - WillDelayInterval = clientOptions.WillDelayInterval, - MaximumPacketSize = clientOptions.MaximumPacketSize, - ReceiveMaximum = clientOptions.ReceiveMaximum, - RequestProblemInformation = clientOptions.RequestProblemInformation, - RequestResponseInformation = clientOptions.RequestResponseInformation, - SessionExpiryInterval = clientOptions.SessionExpiryInterval, - TopicAliasMaximum = clientOptions.TopicAliasMaximum, - UserProperties = clientOptions.UserProperties, - TryPrivate = clientOptions.TryPrivate - }; + throw new ArgumentNullException(nameof(clientOptions)); + } - 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; - } + var connectPacket = new MqttConnectPacket + { + ClientId = clientOptions.ClientId, + Username = clientOptions.Credentials?.GetUserName(clientOptions), + Password = clientOptions.Credentials?.GetPassword(clientOptions), + CleanSession = clientOptions.CleanSession, + KeepAlivePeriod = (ushort)clientOptions.KeepAlivePeriod.TotalSeconds, + AuthenticationMethod = clientOptions.AuthenticationMethod, + AuthenticationData = clientOptions.AuthenticationData, + WillDelayInterval = clientOptions.WillDelayInterval, + MaximumPacketSize = clientOptions.MaximumPacketSize, + ReceiveMaximum = clientOptions.ReceiveMaximum, + RequestProblemInformation = clientOptions.RequestProblemInformation, + RequestResponseInformation = clientOptions.RequestResponseInformation, + SessionExpiryInterval = clientOptions.SessionExpiryInterval, + TopicAliasMaximum = clientOptions.TopicAliasMaximum, + UserProperties = clientOptions.UserProperties, + TryPrivate = clientOptions.TryPrivate + }; - return connectPacket; + 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; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs b/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs index 24362004e..140424d42 100644 --- a/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs @@ -6,77 +6,31 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttDisconnectPacketFactory { - public sealed class MqttDisconnectPacketFactory + static readonly MqttDisconnectPacket DefaultNormalDisconnection = new() { - static readonly MqttDisconnectPacket DefaultNormalDisconnection = new MqttDisconnectPacket - { - ReasonCode = MqttDisconnectReasonCode.NormalDisconnection, - UserProperties = null, - ReasonString = null, - ServerReference = null, - SessionExpiryInterval = 0 - }; - - static readonly MqttDisconnectPacket DefaultServerShuttingDown = new MqttDisconnectPacket - { - ReasonCode = MqttDisconnectReasonCode.ServerShuttingDown, - UserProperties = null, - ReasonString = null, - ServerReference = null, - SessionExpiryInterval = 0 - }; - - static readonly MqttDisconnectPacket DefaultUnspecifiedError = new MqttDisconnectPacket - { - ReasonCode = MqttDisconnectReasonCode.UnspecifiedError, - UserProperties = null, - ReasonString = null, - ServerReference = null, - SessionExpiryInterval = 0 - }; - - public MqttDisconnectPacket Create(MqttDisconnectReasonCode reasonCode) + ReasonCode = MqttDisconnectReasonCode.NormalDisconnection, + UserProperties = null, + ReasonString = null, + ServerReference = null, + SessionExpiryInterval = 0 + }; + + public static MqttDisconnectPacket Create(MqttClientDisconnectOptions clientDisconnectOptions) + { + if (clientDisconnectOptions == null) { - if (reasonCode == MqttDisconnectReasonCode.NormalDisconnection) - { - return DefaultNormalDisconnection; - } - - if (reasonCode == MqttDisconnectReasonCode.ServerShuttingDown) - { - return DefaultServerShuttingDown; - } - - if (reasonCode == MqttDisconnectReasonCode.UnspecifiedError) - { - return DefaultUnspecifiedError; - } - - return new MqttDisconnectPacket - { - ReasonCode = reasonCode, - UserProperties = null, - ReasonString = null, - ServerReference = null, - SessionExpiryInterval = 0 - }; + return DefaultNormalDisconnection; } - public MqttDisconnectPacket Create(MqttClientDisconnectOptions clientDisconnectOptions) + return new MqttDisconnectPacket { - if (clientDisconnectOptions == null) - { - return DefaultNormalDisconnection; - } - - return new MqttDisconnectPacket - { - ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason, - UserProperties = clientDisconnectOptions.UserProperties, - SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval - }; - } + ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason, + UserProperties = clientDisconnectOptions.UserProperties, + SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval + }; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPacketFactories.cs b/Source/MQTTnet/Formatter/MqttPacketFactories.cs deleted file mode 100644 index d4fe4dd92..000000000 --- a/Source/MQTTnet/Formatter/MqttPacketFactories.cs +++ /dev/null @@ -1,27 +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.Formatter -{ - public static class MqttPacketFactories - { - public static MqttConnectPacketFactory Connect { get; } = new(); - - public static MqttDisconnectPacketFactory Disconnect { get; } = new(); - - public static MqttPubAckPacketFactory PubAck { get; } = new(); - - public static MqttPubCompPacketFactory PubComp { get; } = new(); - - public static MqttPublishPacketFactory Publish { get; } = new(); - - public static MqttPubRecPacketFactory PubRec { get; } = new(); - - public static MqttPubRelPacketFactory PubRel { get; } = new(); - - public static MqttSubscribePacketFactory Subscribe { get; } = new(); - - public static MqttUnsubscribePacketFactory Unsubscribe { get; } = new(); - } -} \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs index a0e8e3a4b..d0dfda4a6 100644 --- a/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs @@ -7,26 +7,25 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttPubAckPacketFactory { - public sealed class MqttPubAckPacketFactory + public static MqttPubAckPacket Create(MqttApplicationMessageReceivedEventArgs applicationMessageReceivedEventArgs) { - public MqttPubAckPacket Create(MqttApplicationMessageReceivedEventArgs applicationMessageReceivedEventArgs) + if (applicationMessageReceivedEventArgs == null) { - if (applicationMessageReceivedEventArgs == null) - { - throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs)); - } + throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs)); + } - var pubAckPacket = new MqttPubAckPacket - { - PacketIdentifier = applicationMessageReceivedEventArgs.PublishPacket.PacketIdentifier, - ReasonCode = (MqttPubAckReasonCode)(int)applicationMessageReceivedEventArgs.ReasonCode, - UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties, - ReasonString = applicationMessageReceivedEventArgs.ResponseReasonString - }; + var pubAckPacket = new MqttPubAckPacket + { + PacketIdentifier = applicationMessageReceivedEventArgs.PublishPacket.PacketIdentifier, + ReasonCode = (MqttPubAckReasonCode)(int)applicationMessageReceivedEventArgs.ReasonCode, + UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties, + ReasonString = applicationMessageReceivedEventArgs.ResponseReasonString + }; - return pubAckPacket; - } + return pubAckPacket; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPubCompPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPubCompPacketFactory.cs index fd4b34941..ebc15c461 100644 --- a/Source/MQTTnet/Formatter/MqttPubCompPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPubCompPacketFactory.cs @@ -7,22 +7,21 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttPubCompPacketFactory { - public sealed class MqttPubCompPacketFactory + public static MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode) { - public MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode) + if (pubRelPacket == null) { - if (pubRelPacket == null) - { - throw new ArgumentNullException(nameof(pubRelPacket)); - } - - return new MqttPubCompPacket - { - PacketIdentifier = pubRelPacket.PacketIdentifier, - ReasonCode = (MqttPubCompReasonCode)(int)reasonCode - }; + throw new ArgumentNullException(nameof(pubRelPacket)); } + + return new MqttPubCompPacket + { + PacketIdentifier = pubRelPacket.PacketIdentifier, + ReasonCode = (MqttPubCompReasonCode)(int)reasonCode + }; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs index 3b0ba27a0..33a3094ee 100644 --- a/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs @@ -7,32 +7,31 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttPubRecPacketFactory { - public sealed class MqttPubRecPacketFactory + public static MqttPubRecPacket Create(MqttApplicationMessageReceivedEventArgs applicationMessageReceivedEventArgs) { - public MqttPubRecPacket Create(MqttApplicationMessageReceivedEventArgs applicationMessageReceivedEventArgs) + if (applicationMessageReceivedEventArgs == null) { - if (applicationMessageReceivedEventArgs == null) - { - throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs)); - } + throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs)); + } - var pubRecPacket = Create(applicationMessageReceivedEventArgs.PublishPacket, applicationMessageReceivedEventArgs.ReasonCode); - pubRecPacket.UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties; + var pubRecPacket = Create(applicationMessageReceivedEventArgs.PublishPacket, applicationMessageReceivedEventArgs.ReasonCode); + pubRecPacket.UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties; - return pubRecPacket; - } + return pubRecPacket; + } - static MqttPubRecPacket Create(MqttPublishPacket publishPacket, MqttApplicationMessageReceivedReasonCode applicationMessageReceivedReasonCode) + static MqttPubRecPacket Create(MqttPublishPacket publishPacket, MqttApplicationMessageReceivedReasonCode applicationMessageReceivedReasonCode) + { + var pubRecPacket = new MqttPubRecPacket { - var pubRecPacket = new MqttPubRecPacket - { - PacketIdentifier = publishPacket.PacketIdentifier, - ReasonCode = (MqttPubRecReasonCode)(int)applicationMessageReceivedReasonCode - }; + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = (MqttPubRecReasonCode)(int)applicationMessageReceivedReasonCode + }; - return pubRecPacket; - } + return pubRecPacket; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPubRelPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPubRelPacketFactory.cs index a16790b61..3ba82ecf3 100644 --- a/Source/MQTTnet/Formatter/MqttPubRelPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPubRelPacketFactory.cs @@ -7,22 +7,21 @@ using MQTTnet.Packets; using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttPubRelPacketFactory { - public sealed class MqttPubRelPacketFactory + public static MqttPubRelPacket Create(MqttPubRecPacket pubRecPacket, MqttApplicationMessageReceivedReasonCode reasonCode) { - public MqttPubRelPacket Create(MqttPubRecPacket pubRecPacket, MqttApplicationMessageReceivedReasonCode reasonCode) + if (pubRecPacket == null) { - if (pubRecPacket == null) - { - throw new ArgumentNullException(nameof(pubRecPacket)); - } - - return new MqttPubRelPacket - { - PacketIdentifier = pubRecPacket.PacketIdentifier, - ReasonCode = (MqttPubRelReasonCode)(int)reasonCode - }; + throw new ArgumentNullException(nameof(pubRecPacket)); } + + return new MqttPubRelPacket + { + PacketIdentifier = pubRecPacket.PacketIdentifier, + ReasonCode = (MqttPubRelReasonCode)(int)reasonCode + }; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index 050b051d8..9ec30fe59 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -8,22 +8,9 @@ namespace MQTTnet.Formatter { - public sealed class MqttPublishPacketFactory + public static class MqttPublishPacketFactory { - public MqttPublishPacket Clone(MqttPublishPacket publishPacket) - { - return new MqttPublishPacket - { - Topic = publishPacket.Topic, - PayloadSegment = publishPacket.PayloadSegment, - Retain = publishPacket.Retain, - QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, - Dup = publishPacket.Dup, - PacketIdentifier = publishPacket.PacketIdentifier - }; - } - - public MqttPublishPacket Create(MqttApplicationMessage applicationMessage) + public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage) { if (applicationMessage == null) { @@ -51,40 +38,5 @@ public MqttPublishPacket Create(MqttApplicationMessage applicationMessage) return packet; } - - public MqttPublishPacket Create(MqttConnectPacket connectPacket) - { - if (connectPacket == null) - { - throw new ArgumentNullException(nameof(connectPacket)); - } - - if (!connectPacket.WillFlag) - { - throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag)."); - } - - ArraySegment willMessageBuffer = default; - if (connectPacket.WillMessage?.Length > 0) - { - willMessageBuffer = new ArraySegment(connectPacket.WillMessage); - } - - var packet = new MqttPublishPacket - { - Topic = connectPacket.WillTopic, - PayloadSegment = willMessageBuffer, - QualityOfServiceLevel = connectPacket.WillQoS, - Retain = connectPacket.WillRetain, - ContentType = connectPacket.WillContentType, - CorrelationData = connectPacket.WillCorrelationData, - MessageExpiryInterval = connectPacket.WillMessageExpiryInterval, - PayloadFormatIndicator = connectPacket.WillPayloadFormatIndicator, - ResponseTopic = connectPacket.WillResponseTopic, - UserProperties = connectPacket.WillUserProperties - }; - - return packet; - } } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttSubscribePacketFactory.cs b/Source/MQTTnet/Formatter/MqttSubscribePacketFactory.cs index e24fe9fff..aef7e667a 100644 --- a/Source/MQTTnet/Formatter/MqttSubscribePacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttSubscribePacketFactory.cs @@ -6,25 +6,24 @@ using MQTTnet.Client; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttSubscribePacketFactory { - public sealed class MqttSubscribePacketFactory + public static MqttSubscribePacket Create(MqttClientSubscribeOptions clientSubscribeOptions) { - public MqttSubscribePacket Create(MqttClientSubscribeOptions clientSubscribeOptions) + if (clientSubscribeOptions == null) { - if (clientSubscribeOptions == null) - { - throw new ArgumentNullException(nameof(clientSubscribeOptions)); - } + throw new ArgumentNullException(nameof(clientSubscribeOptions)); + } - var packet = new MqttSubscribePacket - { - TopicFilters = clientSubscribeOptions.TopicFilters, - SubscriptionIdentifier = clientSubscribeOptions.SubscriptionIdentifier, - UserProperties = clientSubscribeOptions.UserProperties - }; + var packet = new MqttSubscribePacket + { + TopicFilters = clientSubscribeOptions.TopicFilters, + SubscriptionIdentifier = clientSubscribeOptions.SubscriptionIdentifier, + UserProperties = clientSubscribeOptions.UserProperties + }; - return packet; - } + return packet; } } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttUnsubscribePacketFactory.cs b/Source/MQTTnet/Formatter/MqttUnsubscribePacketFactory.cs index 85cda9446..ec8bec7a0 100644 --- a/Source/MQTTnet/Formatter/MqttUnsubscribePacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttUnsubscribePacketFactory.cs @@ -6,28 +6,27 @@ using MQTTnet.Client; using MQTTnet.Packets; -namespace MQTTnet.Formatter +namespace MQTTnet.Formatter; + +public static class MqttUnsubscribePacketFactory { - public sealed class MqttUnsubscribePacketFactory + public static MqttUnsubscribePacket Create(MqttClientUnsubscribeOptions clientUnsubscribeOptions) { - public MqttUnsubscribePacket Create(MqttClientUnsubscribeOptions clientUnsubscribeOptions) + if (clientUnsubscribeOptions == null) { - if (clientUnsubscribeOptions == null) - { - throw new ArgumentNullException(nameof(clientUnsubscribeOptions)); - } - - var packet = new MqttUnsubscribePacket - { - UserProperties = clientUnsubscribeOptions.UserProperties - }; + throw new ArgumentNullException(nameof(clientUnsubscribeOptions)); + } - if (clientUnsubscribeOptions.TopicFilters != null) - { - packet.TopicFilters.AddRange(clientUnsubscribeOptions.TopicFilters); - } + var packet = new MqttUnsubscribePacket + { + UserProperties = clientUnsubscribeOptions.UserProperties + }; - return packet; + if (clientUnsubscribeOptions.TopicFilters != null) + { + packet.TopicFilters.AddRange(clientUnsubscribeOptions.TopicFilters); } + + return packet; } } \ No newline at end of file diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index b829aa6e8..8dd3a95aa 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -2,7 +2,7 @@ - + @(ReleaseNotes, '%0a') @@ -57,7 +57,7 @@ - + \ No newline at end of file diff --git a/Source/MQTTnet/MQTTnet.csproj.DotSettings b/Source/MQTTnet/MQTTnet.csproj.DotSettings index f8a9b730b..65deaac13 100644 --- a/Source/MQTTnet/MQTTnet.csproj.DotSettings +++ b/Source/MQTTnet/MQTTnet.csproj.DotSettings @@ -1,21 +1,43 @@ - - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True \ No newline at end of file + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/Source/MQTTnet/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/MqttApplicationMessageExtensions.cs index bf3e71540..c2f82dd8d 100644 --- a/Source/MQTTnet/MqttApplicationMessageExtensions.cs +++ b/Source/MQTTnet/MqttApplicationMessageExtensions.cs @@ -6,29 +6,28 @@ using System.Text; using MQTTnet.Internal; -namespace MQTTnet +namespace MQTTnet; + +public static class MqttApplicationMessageExtensions { - public static class MqttApplicationMessageExtensions + public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage) { - public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage) + if (applicationMessage == null) { - if (applicationMessage == null) - { - throw new ArgumentNullException(nameof(applicationMessage)); - } - - if(applicationMessage.PayloadSegment == EmptyBuffer.ArraySegment) - { - return null; - } + throw new ArgumentNullException(nameof(applicationMessage)); + } - if (applicationMessage.PayloadSegment.Array == null) - { - return null; - } + if (applicationMessage.PayloadSegment == EmptyBuffer.ArraySegment) + { + return null; + } - var payloadSegment = applicationMessage.PayloadSegment; - return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count); + if (applicationMessage.PayloadSegment.Array == null) + { + return null; } + + var payloadSegment = applicationMessage.PayloadSegment; + return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count); } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttAuthPacket.cs b/Source/MQTTnet/Packets/MqttAuthPacket.cs index e29c75199..8c1b710e7 100644 --- a/Source/MQTTnet/Packets/MqttAuthPacket.cs +++ b/Source/MQTTnet/Packets/MqttAuthPacket.cs @@ -5,19 +5,18 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +/// Added in MQTTv5.0.0. +public sealed class MqttAuthPacket : MqttPacket { - /// Added in MQTTv5.0.0. - public sealed class MqttAuthPacket : MqttPacket - { - public byte[] AuthenticationData { get; set; } + public byte[] AuthenticationData { get; set; } + + public string AuthenticationMethod { get; set; } - public string AuthenticationMethod { get; set; } - - public MqttAuthenticateReasonCode ReasonCode { get; set; } + public MqttAuthenticateReasonCode ReasonCode { get; set; } - public string ReasonString { get; set; } + public string ReasonString { get; set; } - public List UserProperties { get; set; } - } + public List UserProperties { get; set; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index 3ae7bc6b5..3906a9004 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -5,62 +5,61 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttConnAckPacket : MqttPacket { - public sealed class MqttConnAckPacket : MqttPacket - { - /// - /// Added in MQTTv5. - /// - public string AssignedClientIdentifier { get; set; } + /// + /// Added in MQTTv5. + /// + public string AssignedClientIdentifier { get; set; } + + public byte[] AuthenticationData { get; set; } - public byte[] AuthenticationData { get; set; } + public string AuthenticationMethod { get; set; } - public string AuthenticationMethod { get; set; } + /// + /// Added in MQTTv3.1.1. + /// + public bool IsSessionPresent { get; set; } - /// - /// Added in MQTTv3.1.1. - /// - public bool IsSessionPresent { get; set; } + public uint MaximumPacketSize { get; set; } - public uint MaximumPacketSize { get; set; } + public MqttQualityOfServiceLevel MaximumQoS { get; set; } - public MqttQualityOfServiceLevel MaximumQoS { get; set; } + /// + /// Added in MQTTv5. + /// + public MqttConnectReasonCode ReasonCode { get; set; } - /// - /// Added in MQTTv5. - /// - public MqttConnectReasonCode ReasonCode { get; set; } + public string ReasonString { get; set; } - public string ReasonString { get; set; } + public ushort ReceiveMaximum { get; set; } - public ushort ReceiveMaximum { get; set; } + public string ResponseInformation { get; set; } - public string ResponseInformation { get; set; } + public bool RetainAvailable { get; set; } - public bool RetainAvailable { get; set; } - - public MqttConnectReturnCode ReturnCode { get; set; } + public MqttConnectReturnCode ReturnCode { get; set; } - public ushort ServerKeepAlive { get; set; } + public ushort ServerKeepAlive { get; set; } - public string ServerReference { get; set; } - - public uint SessionExpiryInterval { get; set; } + public string ServerReference { get; set; } - public bool SharedSubscriptionAvailable { get; set; } + public uint SessionExpiryInterval { get; set; } - public bool SubscriptionIdentifiersAvailable { get; set; } + public bool SharedSubscriptionAvailable { get; set; } - public ushort TopicAliasMaximum { get; set; } + public bool SubscriptionIdentifiersAvailable { get; set; } - public List UserProperties { get; set; } + public ushort TopicAliasMaximum { get; set; } - public bool WildcardSubscriptionAvailable { get; set; } + public List UserProperties { get; set; } - public override string ToString() - { - return $"ConnAck: [ReturnCode={ReturnCode}] [ReasonCode={ReasonCode}] [IsSessionPresent={IsSessionPresent}]"; - } + public bool WildcardSubscriptionAvailable { get; set; } + + public override string ToString() + { + return $"ConnAck: [ReturnCode={ReturnCode}] [ReasonCode={ReasonCode}] [IsSessionPresent={IsSessionPresent}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPacket.cs b/Source/MQTTnet/Packets/MqttPacket.cs index 9d0af89d1..1e07e42fe 100644 --- a/Source/MQTTnet/Packets/MqttPacket.cs +++ b/Source/MQTTnet/Packets/MqttPacket.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.Packets +namespace MQTTnet.Packets; + +public abstract class MqttPacket { - 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 42d2288d4..a85b441f8 100644 --- a/Source/MQTTnet/Packets/MqttPacketExtensions.cs +++ b/Source/MQTTnet/Packets/MqttPacketExtensions.cs @@ -4,98 +4,97 @@ using System; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public static class MqttPacketExtensions { - public static class MqttPacketExtensions + public static string GetRfcName(this MqttPacket packet) { - public static string GetRfcName(this MqttPacket packet) + if (packet == null) + { + throw new ArgumentNullException(nameof(packet)); + } + + switch (packet) { - if (packet == null) - { - throw new ArgumentNullException(nameof(packet)); - } - - switch (packet) - { - 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; - } + 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; } } } diff --git a/Source/MQTTnet/Packets/MqttPingRespPacket.cs b/Source/MQTTnet/Packets/MqttPingRespPacket.cs index d8950614e..17f8dbd07 100644 --- a/Source/MQTTnet/Packets/MqttPingRespPacket.cs +++ b/Source/MQTTnet/Packets/MqttPingRespPacket.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 MqttPingRespPacket : MqttPacket { - public sealed class MqttPingRespPacket : MqttPacket - { - // This is a minor performance improvement. - public static readonly MqttPingRespPacket Instance = new MqttPingRespPacket(); + // This is a minor performance improvement. + public static readonly MqttPingRespPacket Instance = new(); - public override string ToString() - { - return "PingResp"; - } + public override string ToString() + { + return "PingResp"; } -} +} \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPubAckPacket.cs b/Source/MQTTnet/Packets/MqttPubAckPacket.cs index 97053b3e8..6b0c0c5bd 100644 --- a/Source/MQTTnet/Packets/MqttPubAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubAckPacket.cs @@ -5,28 +5,27 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttPubAckPacket : MqttPacketWithIdentifier { - public sealed class MqttPubAckPacket : MqttPacketWithIdentifier - { - /// - /// Added in MQTTv5. - /// - public MqttPubAckReasonCode ReasonCode { get; set; } = MqttPubAckReasonCode.Success; + /// + /// Added in MQTTv5. + /// + public MqttPubAckReasonCode ReasonCode { get; set; } = MqttPubAckReasonCode.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 $"PubAck: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; - } + public override string ToString() + { + return $"PubAck: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPubCompPacket.cs b/Source/MQTTnet/Packets/MqttPubCompPacket.cs index ec4dd8341..557bccffe 100644 --- a/Source/MQTTnet/Packets/MqttPubCompPacket.cs +++ b/Source/MQTTnet/Packets/MqttPubCompPacket.cs @@ -5,28 +5,27 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttPubCompPacket : MqttPacketWithIdentifier { - public sealed class MqttPubCompPacket : MqttPacketWithIdentifier - { - /// - /// Added in MQTTv5. - /// - public MqttPubCompReasonCode ReasonCode { get; set; } = MqttPubCompReasonCode.Success; + /// + /// Added in MQTTv5. + /// + public MqttPubCompReasonCode ReasonCode { get; set; } = MqttPubCompReasonCode.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 $"PubComp: [PacketIdentifier={PacketIdentifier}] [ReasonCode={ReasonCode}]"; - } + public override string ToString() + { + return $"PubComp: [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 3869ea390..c07dddda6 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -6,40 +6,39 @@ using System.Collections.Generic; using MQTTnet.Protocol; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttPublishPacket : MqttPacketWithIdentifier { - public sealed class MqttPublishPacket : MqttPacketWithIdentifier - { - public string ContentType { get; set; } + public string ContentType { get; set; } - public byte[] CorrelationData { get; set; } + public byte[] CorrelationData { get; set; } - public bool Dup { get; set; } + public bool Dup { get; set; } - public uint MessageExpiryInterval { get; set; } + public uint MessageExpiryInterval { get; set; } - public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; + public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - public ArraySegment PayloadSegment { get; set; } + public ArraySegment PayloadSegment { get; set; } - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; - public string ResponseTopic { get; set; } + public string ResponseTopic { get; set; } - public bool Retain { get; set; } + public bool Retain { get; set; } - public List SubscriptionIdentifiers { get; set; } + public List SubscriptionIdentifiers { get; set; } - public string Topic { get; set; } + public string Topic { get; set; } - public ushort TopicAlias { get; set; } + public ushort TopicAlias { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; set; } - public override string ToString() - { - return - $"Publish: [Topic={Topic}] [PayloadLength={PayloadSegment.Count}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; - } + public override string ToString() + { + return + $"Publish: [Topic={Topic}] [PayloadLength={PayloadSegment.Count}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index fdbbaebbe..d1024f4b6 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -5,26 +5,25 @@ using System.Collections.Generic; using System.Linq; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttSubscribePacket : MqttPacketWithIdentifier { - public sealed class MqttSubscribePacket : MqttPacketWithIdentifier - { - /// - /// It is a Protocol Error if the Subscription Identifier has a value of 0. - /// - public uint SubscriptionIdentifier { get; set; } + /// + /// It is a Protocol Error if the Subscription Identifier has a value of 0. + /// + public uint SubscriptionIdentifier { get; set; } - public List TopicFilters { get; set; } = new List(); + public List TopicFilters { get; set; } = new(); - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } - public override string ToString() - { - var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); - return $"Subscribe: [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]"; - } + public override string ToString() + { + var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); + return $"Subscribe: [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs b/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs index cddb3b46a..698bb3301 100644 --- a/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttUnsubscribePacket.cs @@ -4,21 +4,20 @@ using System.Collections.Generic; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttUnsubscribePacket : MqttPacketWithIdentifier { - public sealed class MqttUnsubscribePacket : MqttPacketWithIdentifier - { - public List TopicFilters { get; set; } = new List(); + public List TopicFilters { get; set; } = new(); - /// - /// Added in MQTTv5. - /// - public List UserProperties { get; set; } + /// + /// Added in MQTTv5. + /// + public List UserProperties { get; set; } - public override string ToString() - { - var topicFiltersText = string.Join(",", TopicFilters); - return $"Unsubscribe: [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]"; - } + public override string ToString() + { + var topicFiltersText = string.Join(",", TopicFilters); + return $"Unsubscribe: [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]"; } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttUserProperty.cs b/Source/MQTTnet/Packets/MqttUserProperty.cs index f54c821a1..1486dd865 100644 --- a/Source/MQTTnet/Packets/MqttUserProperty.cs +++ b/Source/MQTTnet/Packets/MqttUserProperty.cs @@ -4,48 +4,47 @@ using System; -namespace MQTTnet.Packets +namespace MQTTnet.Packets; + +public sealed class MqttUserProperty { - public sealed class MqttUserProperty + public MqttUserProperty(string name, string value) { - public MqttUserProperty(string name, string value) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - } + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } - public string Name { get; } + public string Name { get; } - public string Value { get; } + public string Value { get; } - public override bool Equals(object other) - { - return Equals(other as MqttUserProperty); - } + public override bool Equals(object other) + { + return Equals(other as MqttUserProperty); + } - public bool Equals(MqttUserProperty other) + public bool Equals(MqttUserProperty other) + { + if (other == null) { - if (other == null) - { - return false; - } - - if (ReferenceEquals(other, this)) - { - return true; - } - - return string.Equals(Name, other.Name, StringComparison.Ordinal) && string.Equals(Value, other.Value, StringComparison.Ordinal); + return false; } - public override int GetHashCode() + if (ReferenceEquals(other, this)) { - return Name.GetHashCode() ^ Value.GetHashCode(); + return true; } - public override string ToString() - { - return $"{Name} = {Value}"; - } + return string.Equals(Name, other.Name, StringComparison.Ordinal) && string.Equals(Value, other.Value, StringComparison.Ordinal); + } + + public override int GetHashCode() + { + return Name.GetHashCode() ^ Value.GetHashCode(); + } + + public override string ToString() + { + return $"{Name} = {Value}"; } } \ No newline at end of file