diff --git a/.gitignore b/.gitignore index 569e43ac..87343fe5 100644 --- a/.gitignore +++ b/.gitignore @@ -235,6 +235,7 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ +/*/**/package-lock.json # Typescript v1 declaration files typings/ diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 2a8a763f..f5137d18 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -12,12 +12,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). * [Core] Added support for MQTTv5 packages. * [Core] Performance improvements (removed several exceptions). +* [Core] Fixed a memory leak when processing lots of messages (thanks to @tschanko) +* [Core] Added more overloads for MQTT factory. +* [Core] The client password is now hidden from the logs (replaced with **** if set). * [Client] Added new MQTTv5 features to options builder. * [Client] Added uniform API across all supported MQTT versions (BREAKING CHANGE!) * [Client] The client will now avoid sending an ACK if an exception has been thrown in message handler (thanks to @ramonsmits). * [Client] Fixed issues in QoS 2 handling which leads to message loss. +* [Client] Replaced all events with proper async compatible handlers (BREAKING CHANGE!). +* [ManagedClient] Replaced all events with proper async compatible handlers (BREAKING CHANGE!). +* [ManagedClient] The log ID is now propagated to the internal client (thanks to @vbBerni). * [Server] Added support for MQTTv5 clients. The server will still return _success_ for all cases at the moment even if more granular codes are available. * [Server] Fixed issues in QoS 2 handling which leads to message loss. +* [Server] Replaced all events with proper async compatible handlers (BREAKING CHANGE!). +* [Server] The used logger instance is now propagated to the WebSocket server adapter. +* [Server] Added the flag "IsSecureConnection" which is set to true when the connection is encrypted. +* [MQTTnet Server] Added as first Alpha version. * [Note] Due to MQTTv5 a lot of new classes were introduced. This required adding new namespaces as well. Most classes are backward compatible but new namespaces must be added. diff --git a/MQTTnet.noUWP.sln b/MQTTnet.noUWP.sln index f68cbb59..1d24ba22 100644 --- a/MQTTnet.noUWP.sln +++ b/MQTTnet.noUWP.sln @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Tests.csproj", "{9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{DADEA836-5CC3-474C-A2D8-4D0F31118CD7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -201,6 +203,22 @@ Global {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x64.Build.0 = Release|Any CPU {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x86.ActiveCfg = Release|Any CPU {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x86.Build.0 = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|ARM.ActiveCfg = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|ARM.Build.0 = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|x64.ActiveCfg = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|x64.Build.0 = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Debug|x86.Build.0 = Debug|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|Any CPU.Build.0 = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|ARM.ActiveCfg = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|ARM.Build.0 = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|x64.ActiveCfg = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|x64.Build.0 = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|x86.ActiveCfg = Release|Any CPU + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -215,6 +233,7 @@ Global {C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} + {DADEA836-5CC3-474C-A2D8-4D0F31118CD7} = {32A630A7-2598-41D7-B625-204CD906F5FB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/MQTTnet.sln b/MQTTnet.sln index 309b11fe..f2fff839 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -226,6 +228,22 @@ Global {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.Build.0 = Release|Any CPU {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.ActiveCfg = Release|Any CPU {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.Build.0 = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|ARM.Build.0 = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x64.ActiveCfg = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x64.Build.0 = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x86.ActiveCfg = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x86.Build.0 = Debug|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|Any CPU.Build.0 = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|ARM.ActiveCfg = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|ARM.Build.0 = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x64.ActiveCfg = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x64.Build.0 = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x86.ActiveCfg = Release|Any CPU + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,6 +259,7 @@ Global {998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} {61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} + {5699FB8C-838C-4AB0-80A5-9CA809F9B65B} = {32A630A7-2598-41D7-B625-204CD906F5FB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} diff --git a/MQTTnet.sln.DotSettings b/MQTTnet.sln.DotSettings index 023e3d60..20d5d147 100644 --- a/MQTTnet.sln.DotSettings +++ b/MQTTnet.sln.DotSettings @@ -7,6 +7,7 @@ True True True + True True True True diff --git a/README.md b/README.md index b74c1fbe..9ae1bb84 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Uniform API across all supported versions of the MQTT protocol * Interfaces included for mocking and testing * Access to internal trace messages -* Unit tested (~130 tests) +* Unit tested (~150 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_. @@ -51,6 +52,16 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov * Validate subscriptions and deny subscribing of certain topics depending on requesting clients * Connect clients with different protocol versions at the same time. +## MQTTnet Server + +_MQTTnet Server_ is a reference implementation of a MQTT server using this library. It has the following features. +* Running portable (no installation required) +* Python scripting support for manipulating messages, validation of clients etc. +* Runs und Windows, Linux, macOS, Raspberry Pi +* Supports WebSocket and TCP (with and without TLS) connections +* Provides a HTTP based API (including Swagger endpoint) +* Extensive configuration + ## Supported frameworks * .NET Standard 1.3+ diff --git a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs index 2125c7df..4c6a45f0 100644 --- a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs @@ -30,8 +30,7 @@ namespace MQTTnet.AspNetCore var adapter = app.ApplicationServices.GetRequiredService(); using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol)) { - var endpoint = $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}"; - await adapter.RunWebSocketConnectionAsync(webSocket, endpoint); + await adapter.RunWebSocketConnectionAsync(webSocket, context); } }); diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index 999b340c..4a9eb91d 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -20,6 +20,7 @@ namespace MQTTnet.AspNetCore } public string Endpoint => Connection.ConnectionId; + public bool IsSecureConnection => false; // TODO: Fix detection. public ConnectionContext Connection { get; } public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public event EventHandler ReadingPacketStarted; diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index 0c09a7a9..ec2528f0 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -10,7 +10,7 @@ namespace MQTTnet.AspNetCore { public class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter { - public event EventHandler ClientAccepted; + public Action ClientAcceptedHandler { get; set; } public override async Task OnConnectedAsync(ConnectionContext connection) { @@ -24,9 +24,9 @@ namespace MQTTnet.AspNetCore using (var adapter = new MqttConnectionContext(new MqttPacketFormatterAdapter(), connection)) { var args = new MqttServerAdapterClientAcceptedEventArgs(adapter); - ClientAccepted?.Invoke(this, args); + ClientAcceptedHandler?.Invoke(args); - await args.SessionTask; + await args.SessionTask.ConfigureAwait(false); } } diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 65514d0b..959d25a4 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -1,6 +1,7 @@ using System; using System.Net.WebSockets; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Formatter; @@ -11,7 +12,16 @@ namespace MQTTnet.AspNetCore { public class MqttWebSocketServerAdapter : IMqttServerAdapter { - public event EventHandler ClientAccepted; + private readonly IMqttNetChildLogger _logger; + + public MqttWebSocketServerAdapter(IMqttNetChildLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + _logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter)); + } + + public Action ClientAcceptedHandler { get; set; } public Task StartAsync(IMqttServerOptions options) { @@ -23,14 +33,21 @@ namespace MQTTnet.AspNetCore return Task.CompletedTask; } - public async Task RunWebSocketConnectionAsync(WebSocket webSocket, string endpoint) + public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) { if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); - var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketFormatterAdapter(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); + var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; + + var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false); + var isSecureConnection = clientCertificate != null; + clientCertificate?.Dispose(); + + var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection); + var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(), _logger.CreateChildLogger(nameof(MqttWebSocketServerAdapter))); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); - ClientAccepted?.Invoke(this, eventArgs); + ClientAcceptedHandler?.Invoke(eventArgs); if (eventArgs.SessionTask != null) { @@ -40,7 +57,6 @@ namespace MQTTnet.AspNetCore public void Dispose() { - StopAsync().GetAwaiter().GetResult(); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedHandlerDelegate.cs b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedHandlerDelegate.cs new file mode 100644 index 00000000..e976722e --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class ApplicationMessageProcessedHandlerDelegate : IApplicationMessageProcessedHandler + { + private readonly Func _handler; + + public ApplicationMessageProcessedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = context => + { + handler(context); + return Task.FromResult(0); + }; + } + + public ApplicationMessageProcessedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleApplicationMessageProcessedAsync(ApplicationMessageProcessedEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedHandlerDelegate.cs b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedHandlerDelegate.cs new file mode 100644 index 00000000..978f30be --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class ApplicationMessageSkippedHandlerDelegate : IApplicationMessageSkippedHandler + { + private readonly Func _handler; + + public ApplicationMessageSkippedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public ApplicationMessageSkippedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleApplicationMessageSkippedAsync(ApplicationMessageSkippedEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/ConnectingFailedHandlerDelegate.cs b/Source/MQTTnet.Extensions.ManagedClient/ConnectingFailedHandlerDelegate.cs new file mode 100644 index 00000000..85a796b9 --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/ConnectingFailedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class ConnectingFailedHandlerDelegate : IConnectingFailedHandler + { + private readonly Func _handler; + + public ConnectingFailedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public ConnectingFailedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleConnectingFailedAsync(ManagedProcessFailedEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageProcessedHandler.cs b/Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageProcessedHandler.cs new file mode 100644 index 00000000..08ed197a --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageProcessedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public interface IApplicationMessageProcessedHandler + { + Task HandleApplicationMessageProcessedAsync(ApplicationMessageProcessedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageSkippedHandler.cs b/Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageSkippedHandler.cs new file mode 100644 index 00000000..e664cd2f --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageSkippedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public interface IApplicationMessageSkippedHandler + { + Task HandleApplicationMessageSkippedAsync(ApplicationMessageSkippedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/IConnectingFailedHandler.cs b/Source/MQTTnet.Extensions.ManagedClient/IConnectingFailedHandler.cs new file mode 100644 index 00000000..aabdbfb0 --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/IConnectingFailedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public interface IConnectingFailedHandler + { + Task HandleConnectingFailedAsync(ManagedProcessFailedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index 644104f5..9919081f 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -14,19 +14,14 @@ namespace MQTTnet.Extensions.ManagedClient IManagedMqttClientOptions Options { get; } IMqttClientConnectedHandler ConnectedHandler { get; set; } - [Obsolete("Use ConnectedHandler instead.")] - event EventHandler Connected; - IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } - [Obsolete("Use DisconnectedHandler instead.")] - event EventHandler Disconnected; - event EventHandler ApplicationMessageProcessed; - event EventHandler ApplicationMessageSkipped; - - event EventHandler ConnectingFailed; - event EventHandler SynchronizingSubscriptionsFailed; + IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; } + IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; } + IConnectingFailedHandler ConnectingFailedHandler { get; set; } + ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; } + Task StartAsync(IManagedMqttClientOptions options); Task StopAsync(); diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs index 589f7acc..a705efea 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs @@ -1,5 +1,4 @@ using System; -using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Server; diff --git a/Source/MQTTnet.Extensions.ManagedClient/ISynchronizingSubscriptionsFailedHandler.cs b/Source/MQTTnet.Extensions.ManagedClient/ISynchronizingSubscriptionsFailedHandler.cs new file mode 100644 index 00000000..f76f18fb --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/ISynchronizingSubscriptionsFailedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public interface ISynchronizingSubscriptionsFailedHandler + { + Task HandleSynchronizingSubscriptionsFailedAsync(ManagedProcessFailedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 54750a11..7340578e 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -13,8 +13,6 @@ using MQTTnet.Exceptions; using MQTTnet.Internal; using MQTTnet.Protocol; using MQTTnet.Server; -using MqttClientConnectedEventArgs = MQTTnet.Client.Connecting.MqttClientConnectedEventArgs; -using MqttClientDisconnectedEventArgs = MQTTnet.Client.Disconnecting.MqttClientDisconnectedEventArgs; namespace MQTTnet.Extensions.ManagedClient { @@ -36,14 +34,9 @@ namespace MQTTnet.Extensions.ManagedClient public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger) { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); - _mqttClient.UseConnectedHandler(OnConnected); - _mqttClient.UseDisconnectedHandler(OnDisconnected); - _mqttClient.UseReceivedApplicationMessageHandler(OnApplicationMessageReceived); - + if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateChildLogger(nameof(ManagedMqttClient)); } @@ -52,25 +45,31 @@ namespace MQTTnet.Extensions.ManagedClient public int PendingApplicationMessagesCount => _messageQueue.Count; public IManagedMqttClientOptions Options { get; private set; } - public IMqttClientConnectedHandler ConnectedHandler { get; set; } - public event EventHandler Connected; - - public IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } - public event EventHandler Disconnected; - - - public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler + public IMqttClientConnectedHandler ConnectedHandler { - get => _mqttClient.ReceivedApplicationMessageHandler; - set => _mqttClient.ReceivedApplicationMessageHandler = value; + get => _mqttClient.ConnectedHandler; + set => _mqttClient.ConnectedHandler = value; } - public event EventHandler ApplicationMessageReceived; - public event EventHandler ApplicationMessageProcessed; - public event EventHandler ApplicationMessageSkipped; + public IMqttClientDisconnectedHandler DisconnectedHandler + { + get => _mqttClient.DisconnectedHandler; + set => _mqttClient.DisconnectedHandler = value; + } - public event EventHandler ConnectingFailed; - public event EventHandler SynchronizingSubscriptionsFailed; + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler + { + get => _mqttClient.ApplicationMessageReceivedHandler; + set => _mqttClient.ApplicationMessageReceivedHandler = value; + } + + public IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; } + + public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; } + + public IConnectingFailedHandler ConnectingFailedHandler { get; set; } + + public ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; } public async Task StartAsync(IManagedMqttClientOptions options) { @@ -129,28 +128,45 @@ namespace MQTTnet.Extensions.ManagedClient if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); ManagedMqttApplicationMessage removedMessage = null; - lock (_messageQueue) + ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null; + + try { - if (_messageQueue.Count >= Options.MaxPendingMessages) + lock (_messageQueue) { - if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) + if (_messageQueue.Count >= Options.MaxPendingMessages) { - _logger.Verbose("Skipping publish of new application message because internal queue is full."); - ApplicationMessageSkipped?.Invoke(this, new ApplicationMessageSkippedEventArgs(applicationMessage)); - return; + if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) + { + _logger.Verbose("Skipping publish of new application message because internal queue is full."); + applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage); + return; + } + + if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) + { + removedMessage = _messageQueue.RemoveFirst(); + _logger.Verbose("Removed oldest application message from internal queue because it is full."); + applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage); + } } - if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage) + _messageQueue.Enqueue(applicationMessage); + } + } + finally + { + if (applicationMessageSkippedEventArgs != null) + { + var applicationMessageSkippedHandler = ApplicationMessageSkippedHandler; + if (applicationMessageSkippedHandler != null) { - removedMessage = _messageQueue.RemoveFirst(); - _logger.Verbose("Removed oldest application message from internal queue because it is full."); - ApplicationMessageSkipped?.Invoke(this, new ApplicationMessageSkippedEventArgs(removedMessage)); + await applicationMessageSkippedHandler.HandleApplicationMessageSkippedAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false); } } - _messageQueue.Enqueue(applicationMessage); } - + if (_storageManager != null) { if (removedMessage != null) @@ -355,7 +371,7 @@ namespace MQTTnet.Extensions.ManagedClient } finally { - ApplicationMessageProcessed?.Invoke(this, new ApplicationMessageProcessedEventArgs(message, transmitException)); + ApplicationMessageProcessedHandler?.HandleApplicationMessageProcessedAsync(new ApplicationMessageProcessedEventArgs(message, transmitException)).GetAwaiter().GetResult(); } } @@ -398,7 +414,11 @@ namespace MQTTnet.Extensions.ManagedClient _logger.Warning(exception, "Synchronizing subscriptions failed."); _subscriptionsNotPushed = true; - SynchronizingSubscriptionsFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception)); + var synchronizingSubscriptionsFailedHandler = SynchronizingSubscriptionsFailedHandler; + if (SynchronizingSubscriptionsFailedHandler != null) + { + await synchronizingSubscriptionsFailedHandler.HandleSynchronizingSubscriptionsFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); + } } } @@ -416,29 +436,16 @@ namespace MQTTnet.Extensions.ManagedClient } catch (Exception exception) { - ConnectingFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception)); + var connectingFailedHandler = ConnectingFailedHandler; + if (connectingFailedHandler != null) + { + await connectingFailedHandler.HandleConnectingFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); + } + return ReconnectionResult.NotConnected; } } - - private Task OnApplicationMessageReceived(MqttApplicationMessageHandlerContext context) - { - ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(context.SenderClientId, context.ApplicationMessage)); - return Task.FromResult(0); - } - - private Task OnDisconnected(MqttClientDisconnectedEventArgs eventArgs) - { - Disconnected?.Invoke(this, eventArgs); - return DisconnectedHandler?.HandleDisconnectedAsync(eventArgs); - } - - private Task OnConnected(MqttClientConnectedEventArgs eventArgs) - { - Connected?.Invoke(this, eventArgs); - return ConnectedHandler?.HandleConnectedAsync(eventArgs); - } - + private void StartPublishing() { if (_publishingCancellationToken != null) diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs index 811368f7..a0be4b10 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs @@ -60,28 +60,28 @@ namespace MQTTnet.Extensions.ManagedClient return client; } - public static IManagedMqttClient UseReceivedApplicationMessageHandler(this IManagedMqttClient client, Func handler) + public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, Func handler) { if (handler == null) { - client.ReceivedApplicationMessageHandler = null; + client.ApplicationMessageReceivedHandler = null; return client; } - client.ReceivedApplicationMessageHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } - public static IManagedMqttClient UseReceivedApplicationMessageHandler(this IManagedMqttClient client, Action handler) + public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, Action handler) { if (handler == null) { - client.ReceivedApplicationMessageHandler = null; + client.ApplicationMessageReceivedHandler = null; return client; } - client.ReceivedApplicationMessageHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } diff --git a/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedProcessFailedEventArgs.cs similarity index 57% rename from Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs rename to Source/MQTTnet.Extensions.ManagedClient/ManagedProcessFailedEventArgs.cs index 68dc1608..105e02a2 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedProcessFailedEventArgs.cs @@ -2,9 +2,9 @@ namespace MQTTnet.Extensions.ManagedClient { - public class MqttManagedProcessFailedEventArgs : EventArgs + public class ManagedProcessFailedEventArgs : EventArgs { - public MqttManagedProcessFailedEventArgs(Exception exception) + public ManagedProcessFailedEventArgs(Exception exception) { Exception = exception; } diff --git a/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs index 7f400c55..37393ac2 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs @@ -1,19 +1,18 @@ using System; -using MQTTnet.Client; using MQTTnet.Diagnostics; namespace MQTTnet.Extensions.ManagedClient { public static class MqttFactoryExtensions { - public static IManagedMqttClient CreateManagedMqttClient(this IMqttClientFactory factory) + public static IManagedMqttClient CreateManagedMqttClient(this IMqttFactory factory) { if (factory == null) throw new ArgumentNullException(nameof(factory)); - return new ManagedMqttClient(factory.CreateMqttClient(), new MqttNetLogger().CreateChildLogger()); + return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger.CreateChildLogger()); } - public static IManagedMqttClient CreateManagedMqttClient(this IMqttClientFactory factory, IMqttNetLogger logger) + public static IManagedMqttClient CreateManagedMqttClient(this IMqttFactory factory, IMqttNetLogger logger) { if (factory == null) throw new ArgumentNullException(nameof(factory)); if (logger == null) throw new ArgumentNullException(nameof(logger)); diff --git a/Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs b/Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs new file mode 100644 index 00000000..72d6e4a0 --- /dev/null +++ b/Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Extensions.ManagedClient +{ + public class SynchronizingSubscriptionsFailedHandlerDelegate : ISynchronizingSubscriptionsFailedHandler + { + private readonly Func _handler; + + public SynchronizingSubscriptionsFailedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = context => + { + handler(context); + return Task.FromResult(0); + }; + } + + public SynchronizingSubscriptionsFailedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleSynchronizingSubscriptionsFailedAsync(ManagedProcessFailedEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index ebf66ec1..8a279c75 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -13,12 +13,17 @@ namespace MQTTnet.Extensions.Rpc { private readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>(); private readonly IMqttClient _mqttClient; + private readonly RpcAwareApplicationMessageReceivedHandler _applicationMessageReceivedHandler; public MqttRpcClient(IMqttClient mqttClient) { _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); - _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; + _applicationMessageReceivedHandler = new RpcAwareApplicationMessageReceivedHandler( + mqttClient.ApplicationMessageReceivedHandler, + HandleApplicationMessageReceivedAsync); + + _mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler; } public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -45,6 +50,11 @@ namespace MQTTnet.Extensions.Rpc throw new ArgumentException("The method name cannot contain /, + or #."); } + if (!(_mqttClient.ApplicationMessageReceivedHandler is RpcAwareApplicationMessageReceivedHandler)) + { + throw new InvalidOperationException("The application message received handler was modified."); + } + var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}"; var responseTopic = requestTopic + "/response"; @@ -102,26 +112,30 @@ namespace MQTTnet.Extensions.Rpc } } - private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) + private Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) { if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs)) { - return; + return Task.FromResult(0); } if (tcs.Task.IsCompleted || tcs.Task.IsCanceled) { - return; + return Task.FromResult(0); } tcs.TrySetResult(eventArgs.ApplicationMessage.Payload); + + return Task.FromResult(0); } public void Dispose() { + _mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler.OriginalHandler; + foreach (var tcs in _waitingCalls) { - tcs.Value.SetCanceled(); + tcs.Value.TrySetCanceled(); } _waitingCalls.Clear(); diff --git a/Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs b/Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs new file mode 100644 index 00000000..9d90fb19 --- /dev/null +++ b/Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Client.Receiving; + +namespace MQTTnet.Extensions.Rpc +{ + public class RpcAwareApplicationMessageReceivedHandler : IMqttApplicationMessageReceivedHandler + { + private readonly Func _handleReceivedApplicationMessageAsync; + + public RpcAwareApplicationMessageReceivedHandler( + IMqttApplicationMessageReceivedHandler originalHandler, + Func handleReceivedApplicationMessageAsync) + { + OriginalHandler = originalHandler; + _handleReceivedApplicationMessageAsync = handleReceivedApplicationMessageAsync ?? throw new ArgumentNullException(nameof(handleReceivedApplicationMessageAsync)); + } + + public IMqttApplicationMessageReceivedHandler OriginalHandler { get; } + + public async Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + { + if (OriginalHandler != null) + { + await OriginalHandler.HandleApplicationMessageReceivedAsync(eventArgs).ConfigureAwait(false); + } + + await _handleReceivedApplicationMessageAsync(eventArgs).ConfigureAwait(false); + } + } +} diff --git a/Source/MQTTnet.Server/Configuration/ListenModel.cs b/Source/MQTTnet.Server/Configuration/ListenModel.cs new file mode 100644 index 00000000..52c5de42 --- /dev/null +++ b/Source/MQTTnet.Server/Configuration/ListenModel.cs @@ -0,0 +1,132 @@ +using System.IO; +using System.Net; + +namespace MQTTnet.Server.Configuration +{ + /// + /// Listen Entry Settings Model + /// + public class ListenModel + { + /// + /// Constructor + /// + public ListenModel() + { + } + + /// + /// Path to Certificate + /// + public string CertificatePath { get; set; } + + /// + /// Enabled / Disable + /// + public bool Enabled { get; set; } = true; + + /// + /// Listen Address + /// + public string IPv4 { get; set; } + + /// + /// Listen Address + /// + public string IPv6 { get; set; } + + /// + /// Listen Port + /// + public int Port { get; set; } = 1883; + + /// + /// Read Certificate file + /// + /// + public byte[] ReadCertificate() + { + if (string.IsNullOrEmpty(CertificatePath) || string.IsNullOrWhiteSpace(CertificatePath)) + { + throw new FileNotFoundException("No path set"); + } + + if (!File.Exists(CertificatePath)) + { + throw new FileNotFoundException($"Could not find Certificate in path: {CertificatePath}"); + } + + return File.ReadAllBytes(CertificatePath); + } + + /// + /// Read IPv4 + /// + /// + public bool TryReadIPv4(out IPAddress address) + { + if (IPv4 == "*") + { + address = IPAddress.Parse("0.0.0.0"); + return true; + } + + if (IPv4 == "localhost") + { + address = IPAddress.Parse("127.0.0.1"); + return true; + } + + if (IPv4 == "disable") + { + address = IPAddress.None; + return true; + } + + if (IPAddress.TryParse(IPv4, out var ip)) + { + address = ip; + return true; + } + else + { + throw new System.Exception($"Could not parse IPv4 address: {IPv4}"); + } + } + + /// + /// Read IPv6 + /// + /// + public bool TryReadIPv6(out IPAddress address) + { + if (IPv6 == "*") + { + address = IPAddress.Parse("::"); + return true; + } + + if (IPv6 == "localhost") + { + address = IPAddress.Parse("::1"); + return true; + } + + if (IPv6 == "disable") + { + address = IPAddress.None; + return true; + } + + if (IPAddress.TryParse(IPv6, out var ip)) + { + address = ip; + return true; + } + else + { + throw new System.Exception($"Could not parse IPv6 address: {IPv6}"); + } + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Configuration/SettingsModel.cs b/Source/MQTTnet.Server/Configuration/SettingsModel.cs new file mode 100644 index 00000000..ca1ebb47 --- /dev/null +++ b/Source/MQTTnet.Server/Configuration/SettingsModel.cs @@ -0,0 +1,42 @@ +namespace MQTTnet.Server.Configuration +{ + /// + /// Main Settings Model + /// + public class SettingsModel + { + public SettingsModel() + { + } + + /// + /// Set default connection timeout in seconds + /// + public int CommunicationTimeout { get; set; } = 15; + + /// + /// Set 0 to disable connection backlogging + /// + public int ConnectionBacklog { get; set; } + + /// + /// Enable support for persistent sessions + /// + public bool EnablePersistentSessions { get; set; } = false; + + /// + /// Listen Settings + /// + public ListenModel EndPoint { get; set; } = new ListenModel(); + + /// + /// Encryption Listen Settings + /// + public ListenModel EndPointEncryption { get; set; } = new ListenModel(); + + /// + /// Set limit for max pending messages per client + /// + public int MaxPendingMessagesPerClient { get; set; } = 250; + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Controllers/ValuesController.cs b/Source/MQTTnet.Server/Controllers/ValuesController.cs new file mode 100644 index 00000000..bfcc398f --- /dev/null +++ b/Source/MQTTnet.Server/Controllers/ValuesController.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace MQTTnet.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/Source/MQTTnet.Server/LICENSE b/Source/MQTTnet.Server/LICENSE new file mode 100644 index 00000000..7d84e2b1 --- /dev/null +++ b/Source/MQTTnet.Server/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Christian Kratky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Source/MQTTnet.Server/Logging/MqttNetChildLoggerWrapper.cs b/Source/MQTTnet.Server/Logging/MqttNetChildLoggerWrapper.cs new file mode 100644 index 00000000..7f351178 --- /dev/null +++ b/Source/MQTTnet.Server/Logging/MqttNetChildLoggerWrapper.cs @@ -0,0 +1,43 @@ +using System; +using MQTTnet.Diagnostics; + +namespace MQTTnet.Server.Logging +{ + public class MqttNetChildLoggerWrapper : IMqttNetChildLogger + { + private readonly MqttNetLoggerWrapper _logger; + private readonly string _source; + + public MqttNetChildLoggerWrapper(string source, MqttNetLoggerWrapper logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + _source = source; + } + + public IMqttNetChildLogger CreateChildLogger(string source = null) + { + return _logger.CreateChildLogger(source); + } + + public void Verbose(string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Verbose, _source, message, parameters, null); + } + + public void Info(string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Info, _source, message, parameters, null); + } + + public void Warning(Exception exception, string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Warning, _source, message, parameters, exception); + } + + public void Error(Exception exception, string message, params object[] parameters) + { + _logger.Publish(MqttNetLogLevel.Error, _source, message, parameters, exception); + } + } +} diff --git a/Source/MQTTnet.Server/Logging/MqttNetLoggerWrapper.cs b/Source/MQTTnet.Server/Logging/MqttNetLoggerWrapper.cs new file mode 100644 index 00000000..26234514 --- /dev/null +++ b/Source/MQTTnet.Server/Logging/MqttNetLoggerWrapper.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.Extensions.Logging; +using MQTTnet.Diagnostics; + +namespace MQTTnet.Server.Logging +{ + public class MqttNetLoggerWrapper : IMqttNetLogger + { + private readonly ILogger _logger; + + public MqttNetLoggerWrapper(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public event EventHandler LogMessagePublished; + + public IMqttNetChildLogger CreateChildLogger(string source = null) + { + return new MqttNetChildLoggerWrapper(source, this); + } + + public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) + { + var convertedLogLevel = ConvertLogLevel(logLevel); + + if (!_logger.IsEnabled(convertedLogLevel)) + { + return; + } + + _logger.Log(convertedLogLevel, exception, message, parameters); + } + + private static LogLevel ConvertLogLevel(MqttNetLogLevel logLevel) + { + switch (logLevel) + { + case MqttNetLogLevel.Error: return LogLevel.Error; + case MqttNetLogLevel.Warning: return LogLevel.Warning; + case MqttNetLogLevel.Info: return LogLevel.Information; + case MqttNetLogLevel.Verbose: return LogLevel.Trace; + } + + return LogLevel.Debug; + } + } +} diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj new file mode 100644 index 00000000..26ba48aa --- /dev/null +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -0,0 +1,83 @@ + + + + netcoreapp2.2 + InProcess + MQTTnet.Server + MQTTnet.Server + False + + MQTTnet + + + + false + false + latest + 3.3 + + + + + + + + + NU1605 + 1701;1702 + + + + + + + + + Always + + + Never + + + Never + + + Never + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + + + + Never + + + + diff --git a/Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs b/Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs new file mode 100644 index 00000000..156871c1 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using MQTTnet.Adapter; +using MQTTnet.Diagnostics; +using MQTTnet.Server.Logging; + +namespace MQTTnet.Server.Mqtt +{ + public class CustomMqttFactory + { + private readonly MqttFactory _mqttFactory; + + public CustomMqttFactory(ILogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + Logger = new MqttNetLoggerWrapper(logger); + + _mqttFactory = new MqttFactory(Logger); + } + + public IMqttNetLogger Logger { get; } + + public IMqttServer CreateMqttServer(List adapters) + { + if (adapters == null) throw new ArgumentNullException(nameof(adapters)); + + return _mqttFactory.CreateMqttServer(adapters); + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs new file mode 100644 index 00000000..ab3d589c --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Protocol; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttApplicationMessageInterceptor : IMqttServerApplicationMessageInterceptor + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttApplicationMessageInterceptor(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context) + { + try + { + var pythonContext = new PythonDictionary + { + { "accept_publish", context.AcceptPublish }, + { "close_connection", context.CloseConnection }, + { "client_id", context.ClientId }, + { "topic", context.ApplicationMessage.Topic }, + { "qos", (int)context.ApplicationMessage.QualityOfServiceLevel }, + { "retain", context.ApplicationMessage.Retain } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_intercept_application_message", pythonContext); + + context.AcceptPublish = (bool)pythonContext.get("accept_publish", context.AcceptPublish); + context.CloseConnection = (bool)pythonContext.get("close_connection", context.CloseConnection); + context.ApplicationMessage.Topic = (string)pythonContext.get("topic", context.ApplicationMessage.Topic); + context.ApplicationMessage.QualityOfServiceLevel = (MqttQualityOfServiceLevel)(int)pythonContext.get("qos", (int)context.ApplicationMessage.QualityOfServiceLevel); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while intercepting application message."); + } + + return Task.CompletedTask; + } + + // TODO: Create dump(object) method in wrapper (creates JSON and prints it). + public class PythonMqttApplicationMessageInterceptorContext + { + public bool accept_connection; + + public bool accept_publish; + + public string client_id; + + public string topic; + + public int qos; + + public bool retain; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientConnectedHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientConnectedHandler.cs new file mode 100644 index 00000000..8d3879e7 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttClientConnectedHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttClientConnectedHandler : IMqttServerClientConnectedHandler + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttClientConnectedHandler(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs) + { + try + { + var pythonEventArgs = new PythonDictionary + { + { "client_id", eventArgs.ClientId } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_client_connected", pythonEventArgs); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while handling client connected event."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs new file mode 100644 index 00000000..f7b364e8 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttClientDisconnectedHandler : IMqttServerClientDisconnectedHandler + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttClientDisconnectedHandler(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs) + { + try + { + var pythonEventArgs = new PythonDictionary + { + { "client_id", eventArgs.ClientId }, + { "type", PythonConvert.Pythonfy(eventArgs.DisconnectType) } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_client_disconnected", pythonEventArgs); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while handling client disconnected event."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs new file mode 100644 index 00000000..6d90b448 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttClientSubscribedTopicHandler : IMqttServerClientSubscribedTopicHandler + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttClientSubscribedTopicHandler(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs) + { + try + { + var pythonEventArgs = new PythonDictionary + { + { "client_id", eventArgs.ClientId }, + { "topic", eventArgs.TopicFilter.Topic }, + { "qos", (int)eventArgs.TopicFilter.QualityOfServiceLevel } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_client_subscribed_topic", pythonEventArgs); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while handling client subscribed topic event."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs new file mode 100644 index 00000000..ac42f9ee --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttClientUnsubscribedTopicHandler : IMqttServerClientUnsubscribedTopicHandler + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttClientUnsubscribedTopicHandler(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs) + { + try + { + var pythonEventArgs = new PythonDictionary + { + { "client_id", eventArgs.ClientId }, + { "topic", eventArgs.TopicFilter } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_client_unsubscribed_topic", pythonEventArgs); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while handling client unsubscribed topic event."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs new file mode 100644 index 00000000..83f66039 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Protocol; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttConnectionValidator : IMqttServerConnectionValidator + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttConnectionValidator(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task ValidateConnectionAsync(MqttConnectionValidatorContext context) + { + try + { + var pythonContext = new PythonDictionary + { + { "client_id", context.ClientId }, + { "endpoint", context.Endpoint }, + { "is_secure_connection", context.IsSecureConnection }, + { "username", context.Username }, + { "password", context.Password }, + { "result", PythonConvert.Pythonfy(context.ReturnCode) } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_validate_client_connection", pythonContext); + + context.ReturnCode = PythonConvert.ParseEnum((string)pythonContext["result"]); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while validating client connection."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs new file mode 100644 index 00000000..c17b2413 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Adapter; +using MQTTnet.AspNetCore; +using MQTTnet.Implementations; +using MQTTnet.Protocol; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttServerService + { + private readonly ILogger _logger; + + private readonly Configuration.SettingsModel _settings; + private readonly MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor; + private readonly MqttClientConnectedHandler _mqttClientConnectedHandler; + private readonly MqttClientDisconnectedHandler _mqttClientDisconnectedHandler; + private readonly MqttClientSubscribedTopicHandler _mqttClientSubscribedTopicHandler; + private readonly MqttClientUnsubscribedTopicHandler _mqttClientUnsubscribedTopicHandler; + private readonly MqttConnectionValidator _mqttConnectionValidator; + private readonly IMqttServer _mqttServer; + private readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor; + private readonly PythonScriptHostService _pythonScriptHostService; + + public MqttServerService( + Configuration.SettingsModel settings, + CustomMqttFactory mqttFactory, + MqttClientConnectedHandler mqttClientConnectedHandler, + MqttClientDisconnectedHandler mqttClientDisconnectedHandler, + MqttClientSubscribedTopicHandler mqttClientSubscribedTopicHandler, + MqttClientUnsubscribedTopicHandler mqttClientUnsubscribedTopicHandler, + MqttConnectionValidator mqttConnectionValidator, + MqttSubscriptionInterceptor mqttSubscriptionInterceptor, + MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, + PythonScriptHostService pythonScriptHostService, + ILogger logger) + { + _settings = settings; + _mqttClientConnectedHandler = mqttClientConnectedHandler ?? throw new ArgumentNullException(nameof(mqttClientConnectedHandler)); + _mqttClientDisconnectedHandler = mqttClientDisconnectedHandler ?? throw new ArgumentNullException(nameof(mqttClientDisconnectedHandler)); + _mqttClientSubscribedTopicHandler = mqttClientSubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientSubscribedTopicHandler)); + _mqttClientUnsubscribedTopicHandler = mqttClientUnsubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientUnsubscribedTopicHandler)); + _mqttConnectionValidator = mqttConnectionValidator ?? throw new ArgumentNullException(nameof(mqttConnectionValidator)); + _mqttSubscriptionInterceptor = mqttSubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttSubscriptionInterceptor)); + _mqttApplicationMessageInterceptor = mqttApplicationMessageInterceptor ?? throw new ArgumentNullException(nameof(mqttApplicationMessageInterceptor)); + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + WebSocketServerAdapter = new MqttWebSocketServerAdapter(mqttFactory.Logger.CreateChildLogger()); + + var adapters = new List + { + new MqttTcpServerAdapter(mqttFactory.Logger.CreateChildLogger()), + WebSocketServerAdapter + }; + + _mqttServer = mqttFactory.CreateMqttServer(adapters); + } + + public MqttWebSocketServerAdapter WebSocketServerAdapter { get; } + + public void Configure() + { + _pythonScriptHostService.RegisterProxyObject("publish", new Action(Publish)); + + var options = new MqttServerOptionsBuilder() + .WithMaxPendingMessagesPerClient(_settings.MaxPendingMessagesPerClient) + .WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(_settings.CommunicationTimeout)) + .WithConnectionValidator(_mqttConnectionValidator) + .WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) + .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor); + + // Configure unencrypted connections + if (_settings.EndPoint.Enabled) + { + options.WithDefaultEndpoint(); + if (_settings.EndPoint.TryReadIPv4(out var address4)) + { + options.WithDefaultEndpointBoundIPAddress(address4); + } + + if (_settings.EndPoint.TryReadIPv6(out var address6)) + { + options.WithDefaultEndpointBoundIPV6Address(address6); + } + + if (_settings.EndPoint.Port > 0) + { + options.WithDefaultEndpointPort(_settings.EndPoint.Port); + } + } + else + { + options.WithoutDefaultEndpoint(); + } + + // Configure encrypted connections + if (_settings.EndPointEncryption.Enabled) + { + options + .WithEncryptedEndpoint() + .WithEncryptionSslProtocol(System.Security.Authentication.SslProtocols.Tls12) + .WithEncryptionCertificate(_settings.EndPointEncryption.ReadCertificate()); + + if (_settings.EndPointEncryption.TryReadIPv4(out var address4)) + { + options.WithEncryptedEndpointBoundIPAddress(address4); + } + + if (_settings.EndPointEncryption.TryReadIPv6(out var address6)) + { + options.WithEncryptedEndpointBoundIPV6Address(address6); + } + + if (_settings.EndPoint.Port > 0) + { + options.WithEncryptedEndpointPort(_settings.EndPointEncryption.Port); + } + } + else + { + options.WithoutEncryptedEndpoint(); + } + + if (_settings.ConnectionBacklog > 0) + { + options.WithConnectionBacklog(_settings.ConnectionBacklog); + } + + _mqttServer.ClientConnectedHandler = _mqttClientConnectedHandler; + _mqttServer.ClientDisconnectedHandler = _mqttClientDisconnectedHandler; + _mqttServer.ClientSubscribedTopicHandler = _mqttClientSubscribedTopicHandler; + _mqttServer.ClientUnsubscribedTopicHandler = _mqttClientUnsubscribedTopicHandler; + + _mqttServer.StartAsync(options.Build()).GetAwaiter().GetResult(); + + _logger.LogInformation("MQTT server started."); + } + + private void Publish(PythonDictionary parameters) + { + try + { + var applicationMessageBuilder = new MqttApplicationMessageBuilder() + .WithTopic((string)parameters.get("topic", null)) + .WithRetainFlag((bool)parameters.get("retain", false)) + .WithQualityOfServiceLevel((MqttQualityOfServiceLevel)(int)parameters.get("qos", 0)); + + var payload = parameters.get("payload", null); + byte[] binaryPayload; + + if (payload == null) + { + binaryPayload = new byte[0]; + } + else if (payload is string stringPayload) + { + binaryPayload = Encoding.UTF8.GetBytes(stringPayload); + } + else if (payload is ByteArray byteArray) + { + binaryPayload = byteArray.ToArray(); + } + else if (payload is IEnumerable intArray) + { + binaryPayload = intArray.Select(Convert.ToByte).ToArray(); + } + else + { + throw new NotSupportedException("Payload type not supported."); + } + + applicationMessageBuilder = applicationMessageBuilder + .WithPayload(binaryPayload); + + var applicationMessage = applicationMessageBuilder.Build(); + + _mqttServer.PublishAsync(applicationMessage).GetAwaiter().GetResult(); + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while publishing application message from server."); + } + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs new file mode 100644 index 00000000..2d37f747 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using IronPython.Runtime; +using Microsoft.Extensions.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttSubscriptionInterceptor : IMqttServerSubscriptionInterceptor + { + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public MqttSubscriptionInterceptor(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context) + { + try + { + var pythonContext = new PythonDictionary + { + { "accept_subscription", context.AcceptSubscription }, + { "close_connection", context.CloseConnection }, + + { "client_id", context.ClientId }, + { "topic", context.TopicFilter.Topic }, + { "qos", (int)context.TopicFilter.QualityOfServiceLevel } + }; + + _pythonScriptHostService.InvokeOptionalFunction("on_intercept_subscription", pythonContext); + + context.AcceptSubscription = (bool)pythonContext["accept_subscription"]; + context.CloseConnection = (bool)pythonContext["close_connection"]; + } + catch (Exception exception) + { + _logger.LogError(exception, "Error while intercepting subscription."); + } + + return Task.CompletedTask; + } + } +} diff --git a/Source/MQTTnet.Server/Program.cs b/Source/MQTTnet.Server/Program.cs new file mode 100644 index 00000000..9161f166 --- /dev/null +++ b/Source/MQTTnet.Server/Program.cs @@ -0,0 +1,67 @@ +using System; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace MQTTnet.Server +{ + public static class Program + { + public static int Main(string[] args) + { + try + { + PrintLogo(); + + CreateWebHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception exception) + { + Console.WriteLine(exception); + return -1; + } + } + + private static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel() + .UseStartup(); + + private static void PrintLogo() + { + Console.ResetColor(); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(@" + __ __ ____ _______ _______ _ _____ + | \/ |/ __ \__ __|__ __| | | / ____| + | \ / | | | | | | | |_ __ ___| |_ | (___ ___ _ ____ _____ _ __ + | |\/| | | | | | | | | '_ \ / _ \ __| \___ \ / _ \ '__\ \ / / _ \ '__| + | | | | |__| | | | | | | | | __/ |_ ____) | __/ | \ V / __/ | + |_| |_|\___\_\ |_| |_|_| |_|\___|\__| |_____/ \___|_| \_/ \___|_|"); + + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(@" + -- The official MQTT server implementation of MQTTnet -- + Copyright (c) 2017-2019 The MQTTnet Team"); + + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine(@" + https://github.com/chkr1011/MQTTnet"); + + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(@" + Version: 1.0.0-alpha1 + License: MIT (read LICENSE file) + Sponsoring: https://opencollective.com/mqttnet + Support: https://github.com/chkr1011/MQTTnet/issues + Docs: https://github.com/chkr1011/MQTTnet/wiki/MQTTnetServer +"); + + Console.BackgroundColor = ConsoleColor.White; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(" ! THIS IS AN ALPHA VERSION! IT IS NOT RECOMMENDED TO USE IT FOR ANY DIFFERENT PURPOSE THAN TESTING OR EVALUATING!"); + Console.WriteLine(); + Console.ResetColor(); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs b/Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs new file mode 100644 index 00000000..41b6854b --- /dev/null +++ b/Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.Server.Scripting.DataSharing +{ + public class DataSharingService + { + private readonly Dictionary _storage = new Dictionary(); + private readonly PythonScriptHostService _pythonScriptHostService; + private readonly ILogger _logger; + + public DataSharingService(PythonScriptHostService pythonScriptHostService, ILogger logger) + { + _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void Configure() + { + _pythonScriptHostService.RegisterProxyObject("write_shared_data", new Action(Write)); + _pythonScriptHostService.RegisterProxyObject("read_shared_data", new Func(Read)); + } + + public void Write(string key, object value) + { + lock (_storage) + { + _storage[key] = value; + _logger.LogInformation($"Shared data with key '{key}' updated."); + } + } + + public object Read(string key, object defaultValue) + { + lock (_storage) + { + if (!_storage.TryGetValue(key, out var value)) + { + return defaultValue; + } + + return value; + } + } + } +} diff --git a/Source/MQTTnet.Server/Scripting/PythonConvert.cs b/Source/MQTTnet.Server/Scripting/PythonConvert.cs new file mode 100644 index 00000000..532b691e --- /dev/null +++ b/Source/MQTTnet.Server/Scripting/PythonConvert.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; +using System.Text; +using IronPython.Runtime; + +namespace MQTTnet.Server.Scripting +{ + public static class PythonConvert + { + public static object ToPython(object value) + { + if (value is PythonDictionary) + { + return value; + } + + if (value is string) + { + return value; + } + + if (value is int) + { + return value; + } + + if (value is float) + { + return value; + } + + if (value is bool) + { + return value; + } + + if (value is IDictionary dictionary) + { + var pythonDictionary = new PythonDictionary(); + foreach (DictionaryEntry dictionaryEntry in dictionary) + { + pythonDictionary.Add(dictionaryEntry.Key, ToPython(dictionaryEntry.Value)); + } + + return pythonDictionary; + } + + if (value is IEnumerable enumerable) + { + var pythonList = new List(); + foreach (var item in enumerable) + { + pythonList.Add(ToPython(item)); + } + + return pythonList; + } + + return value; + } + + public static string Pythonfy(Enum value) + { + return Pythonfy(value.ToString()); + } + + public static string Pythonfy(string value) + { + var result = new StringBuilder(); + foreach (var @char in value) + { + if (char.IsUpper(@char) && result.Length > 0) + { + result.Append('_'); + } + + result.Append(char.ToLowerInvariant(@char)); + } + + return result.ToString(); + } + + public static TEnum ParseEnum(string value) where TEnum : Enum + { + return (TEnum)Enum.Parse(typeof(TEnum), value.Replace("_", string.Empty), true); + } + } +} diff --git a/Source/MQTTnet.Server/Scripting/PythonIOStream.cs b/Source/MQTTnet.Server/Scripting/PythonIOStream.cs new file mode 100644 index 00000000..b346e896 --- /dev/null +++ b/Source/MQTTnet.Server/Scripting/PythonIOStream.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Text; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.Server.Scripting +{ + public class PythonIOStream : Stream + { + private readonly ILogger _logger; + private readonly Encoding _encoder = Encoding.UTF8; + + public PythonIOStream(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + + if (count == 0) + { + return; + } + + var text = _encoder.GetString(buffer, offset, count); + if (text.Equals(Environment.NewLine)) + { + return; + } + + _logger.LogDebug(text); + } + + public override bool CanRead { get; } = false; + public override bool CanSeek { get; } = false; + public override bool CanWrite { get; } = true; + public override long Length { get; } = 0L; + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + } +} diff --git a/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs b/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs new file mode 100644 index 00000000..1acc58c4 --- /dev/null +++ b/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.Scripting; +using Microsoft.Scripting.Hosting; + +namespace MQTTnet.Server.Scripting +{ + public class PythonScriptHostService + { + private readonly IDictionary _proxyObjects = new ExpandoObject(); + private readonly List _scriptInstances = new List(); + private readonly ILogger _logger; + private readonly ScriptEngine _scriptEngine; + + public PythonScriptHostService(PythonIOStream pythonIOStream, ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + _scriptEngine = IronPython.Hosting.Python.CreateEngine(); + _scriptEngine.Runtime.IO.SetOutput(pythonIOStream, Encoding.UTF8); + } + + public void Configure() + { + AddSearchPaths(_scriptEngine); + + var scriptsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts"); + foreach (var filename in Directory.GetFiles(scriptsDirectory, "*.py", SearchOption.AllDirectories).OrderBy(file => file)) + { + TryInitializeScript(filename); + } + } + + public void RegisterProxyObject(string name, object @object) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (@object == null) throw new ArgumentNullException(nameof(@object)); + + _proxyObjects.Add(name, @object); + } + + public void InvokeOptionalFunction(string name, object parameters) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + lock (_scriptInstances) + { + foreach (var pythonScriptInstance in _scriptInstances) + { + try + { + pythonScriptInstance.InvokeOptionalFunction(name, parameters); + } + catch (Exception exception) + { + _logger.LogError(exception, $"Error while invoking function '{name}' at script '{pythonScriptInstance.Name}'."); + } + } + } + } + + private void TryInitializeScript(string filename) + { + try + { + var scriptName = new FileInfo(filename).Name; + + _logger.LogTrace($"Initializing Python script '{scriptName}'..."); + var code = File.ReadAllText(filename); + + var scriptInstance = CreateScriptInstance(scriptName, code); + scriptInstance.InvokeOptionalFunction("initialize"); + + _scriptInstances.Add(scriptInstance); + + _logger.LogInformation($"Initialized script '{scriptName}'."); + } + catch (Exception exception) + { + _logger.LogError(exception, $"Error while initializing script '{new FileInfo(filename).Name}'."); + } + } + + private PythonScriptInstance CreateScriptInstance(string name, string code) + { + var scriptScope = _scriptEngine.CreateScope(); + + var source = scriptScope.Engine.CreateScriptSourceFromString(code, SourceCodeKind.File); + var compiledCode = source.Compile(); + + scriptScope.SetVariable("mqtt_net_server", _proxyObjects); + compiledCode.Execute(scriptScope); + + return new PythonScriptInstance(name, scriptScope); + } + + private void AddSearchPaths(ScriptEngine scriptEngine) + { + var paths = new List + { + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Lib"), + "/usr/lib/python2.7", + @"C:\Python27\Lib" + }; + + AddSearchPaths(scriptEngine, paths); + } + + private void AddSearchPaths(ScriptEngine scriptEngine, IEnumerable paths) + { + var searchPaths = scriptEngine.GetSearchPaths(); + + foreach (var path in paths) + { + if (Directory.Exists(path)) + { + searchPaths.Add(path); + _logger.LogInformation($"Added Python lib path: {path}"); + } + } + + scriptEngine.SetSearchPaths(searchPaths); + } + } +} diff --git a/Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs b/Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs new file mode 100644 index 00000000..91a46340 --- /dev/null +++ b/Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs @@ -0,0 +1,57 @@ +using System; +using IronPython.Runtime; +using Microsoft.Scripting.Hosting; + +namespace MQTTnet.Server.Scripting +{ + public class PythonScriptInstance + { + private readonly ScriptScope _scriptScope; + + public PythonScriptInstance(string name, ScriptScope scriptScope) + { + _scriptScope = scriptScope; + + Name = name; + } + + public string Name { get; } + + public bool InvokeOptionalFunction(string name, params object[] parameters) + { + return InvokeOptionalFunction(name, parameters, out _); + } + + public bool InvokeOptionalFunction(string name, object[] parameters, out object result) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + lock (_scriptScope) + { + if (!_scriptScope.Engine.Operations.TryGetMember(_scriptScope, name, out var member)) + { + result = null; + return false; + } + + if (!(member is PythonFunction function)) + { + throw new Exception($"Member '{name}' is no Python function."); + } + + try + { + result = _scriptScope.Engine.Operations.Invoke(function, parameters); + return true; + } + catch (Exception exception) + { + var details = _scriptScope.Engine.GetService().FormatException(exception); + var message = $"Error while invoking function '{name}'. " + Environment.NewLine + details; + + throw new Exception(message, exception); + } + } + } + } +} diff --git a/Source/MQTTnet.Server/Scripts/00_sample.py b/Source/MQTTnet.Server/Scripts/00_sample.py new file mode 100644 index 00000000..845a2164 --- /dev/null +++ b/Source/MQTTnet.Server/Scripts/00_sample.py @@ -0,0 +1,107 @@ +import json + + +def initialize(): + """ + This function is invoked after the script file has been loaded. + It will be executed only one time. + """ + + print("Hello World from Sample script.") + + +def on_validate_client_connection(context): + """ + This function is invoked whenever a client wants to connect. It can be used to validate the connection. + """ + + print(context) + + mqtt_net_server.write_shared_data(context["client_id"], {"custom_value_1": 1, "custom_value_2": True}) + + return + + if context["client_id"] != "test_client": + context["result"] = "connection_refused_not_authorized" + return + + if context["username"] != "bud spencer": + context["result"] = "connection_refused_not_authorized" + return + + if context["password"] != "secret": + context["result"] = "connection_refused_not_authorized" + + print(context) + + +def on_intercept_subscription(context): + """ + This function is invoked whenever a client wants to subscribe to a topic. + """ + + print("Client '{client_id}' want's to subscribe to topic '{topic}'.".format(client_id=context["client_id"], topic=context["topic"])) + + +def on_intercept_application_message(context): + """ + This function is invoked for every processed application message. It also allows modifying + the message or cancel processing at all. + """ + + client_id = context["client_id"] + + if client_id != None: + shared_data = mqtt_net_server.read_shared_data(context["client_id"], {}) + print(shared_data) + + if context["topic"] == "topic_with_response": + + json_payload = { + "hello": "world", + "x": 1, + "y": True, + "z": None + } + + application_message = { + "retain": False, + "topic": "reply", + "payload": json.dumps(json_payload) + } + + mqtt_net_server.publish(application_message) + + print("Client '{client_id}' published topic '{topic}'.".format(client_id=context["client_id"], topic=context["topic"])) + + +def on_client_connected(event_args): + """ + This function is called whenever a client has passed the validation is connected. + """ + + print("Client '{client_id}' is now connected.".format(client_id=event_args["client_id"])) + + +def on_client_disconnected(event_args): + """ + This function is called whenever a client has disconnected. + """ + + print("Client '{client_id}' is now disconnected (type = {type}).".format(client_id=event_args["client_id"], type=event_args["type"])) + + +def on_client_subscribed_topic(event_args): + """ + This function is called whenever a client has subscribed to a topic (when allowed). + """ + + print("Client '{client_id}' has subscribed to '{topic}'.".format(client_id=event_args["client_id"], topic=event_args["topic"])) + + +def on_client_unsubscribed_topic(event_args): + """ + This function is called whenever a client has unsubscribed from a topic. + """ + + print("Client '{client_id}' has unsubscribed from '{topic}'.".format(client_id=event_args["client_id"], topic=event_args["topic"])) \ No newline at end of file diff --git a/Source/MQTTnet.Server/Scripts/readme.md b/Source/MQTTnet.Server/Scripts/readme.md new file mode 100644 index 00000000..fb4cc996 --- /dev/null +++ b/Source/MQTTnet.Server/Scripts/readme.md @@ -0,0 +1,19 @@ +This directory contains scripts which are loaded by the server and can be used to perform the following tasks. + +1. Validation of client connections via credentials, client IDs etc. +2. Manipulation of every processed message. +3. Validation of subscription attempts. +4. Publishing of custom application messages. + +All scripts are loaded and _MQTTnet Server_ will invoke functions according to predefined naming conventions. +If a function is implemented in multiple scripts the context will be moved throug all instances. This allows overriding of results or passing data to other (following) scripts. + +The Python starndard library ships with _MQTTnet Server_. But it is possible to add custom paths with Python libraries. + +``` +import sys +sys.path.append(PATH_TO_LIBRARY) +``` + +* All scripts must have the file extension _.py_. +* All scripts are sorted alphabetically (A to Z) before being loaded and parsed. \ No newline at end of file diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs new file mode 100644 index 00000000..058e7e94 --- /dev/null +++ b/Source/MQTTnet.Server/Startup.cs @@ -0,0 +1,110 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.Server.Logging; +using MQTTnet.Server.Mqtt; +using MQTTnet.Server.Scripting; +using MQTTnet.Server.Scripting.DataSharing; + +namespace MQTTnet.Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + public void Configure( + IApplicationBuilder application, + IHostingEnvironment environment, + MqttServerService mqttServerService, + PythonScriptHostService pythonScriptHostService, + DataSharingService dataSharingService) + { + if (environment.IsDevelopment()) + { + application.UseDeveloperExceptionPage(); + } + else + { + application.UseHsts(); + } + + application.UseStaticFiles(); + + var webSocketOptions = new WebSocketOptions + { + KeepAliveInterval = TimeSpan.FromSeconds(120), + ReceiveBufferSize = 4 * 1024 + }; + + application.UseWebSockets(webSocketOptions); + + application.UseHttpsRedirection(); + application.UseMvc(); + + application.Use(async (context, next) => + { + if (context.Request.Path == "/mqtt") // TODO: Full path to config. + { + if (context.WebSockets.IsWebSocketRequest) + { + using (var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false)) + { + await mqttServerService.WebSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, context).ConfigureAwait(false); + } + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next().ConfigureAwait(false); + } + }); + + dataSharingService.Configure(); + pythonScriptHostService.Configure(); + mqttServerService.Configure(); + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + + // Read settings + var settings = new Configuration.SettingsModel(); + Configuration.Bind("MQTT", settings); + services.AddSingleton(settings); + + // Wire up dependencies + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/appsettings.Development.json b/Source/MQTTnet.Server/appsettings.Development.json new file mode 100644 index 00000000..e203e940 --- /dev/null +++ b/Source/MQTTnet.Server/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Source/MQTTnet.Server/appsettings.json b/Source/MQTTnet.Server/appsettings.json new file mode 100644 index 00000000..c913f388 --- /dev/null +++ b/Source/MQTTnet.Server/appsettings.json @@ -0,0 +1,43 @@ +{ + "Kestrel": { + "EndPoints": { + "Http": { + "Url": "http://localhost:80" + }, + "Https": { + "Url": "http://localhost:443" + } + } + }, + "MQTT": { + /* + Wildcard Addresses: + * - All local IP addresses + localhost - Localhost only + disable - Skip address assignment + */ + "EndPoint": { + "Enabled": true, + "IPv4": "localhost", + "IPv6": "localhost", + "Port": 1883 + }, + "EndPointEncryption": { + "Enabled": false, + "IPv4": "localhost", + "IPv6": "localhost", + "Port": 8883, + "CertificatePath": "/absolute/path/to/pfx" + }, + "CommunicationTimeout": 15, /* In seconds */ + "ConnectionBacklog": 0, /* Set 0 to disable */ + "EnablePersistentSessions": false, + "MaxPendingMessagesPerClient": 250 + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Source/MQTTnet.Server/package.json b/Source/MQTTnet.Server/package.json new file mode 100644 index 00000000..5b2909f1 --- /dev/null +++ b/Source/MQTTnet.Server/package.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "name": "mqtt.test", + "private": true, + "devDependencies": {}, + "dependencies": { + "@types/node": "^8.0.46", + "mqtt": "^2.15.1", + "systemjs": "^0.20.19", + "typescript": "^2.3.4" + } +} diff --git a/Source/MQTTnet.Server/tsconfig.json b/Source/MQTTnet.Server/tsconfig.json new file mode 100644 index 00000000..5794ac5b --- /dev/null +++ b/Source/MQTTnet.Server/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "noImplicitAny": false, + "noEmitOnError": true, + "removeComments": false, + "sourceMap": true, + "target": "es5" + }, + "exclude": [ + "node_modules" + ] +} diff --git a/Source/MQTTnet.Server/wwwroot/Index.html b/Source/MQTTnet.Server/wwwroot/Index.html new file mode 100644 index 00000000..5ee25faf --- /dev/null +++ b/Source/MQTTnet.Server/wwwroot/Index.html @@ -0,0 +1,35 @@ + + + + + MQTTnet Server test + + + + + +
+

+ + + +
+ +
+
    +
    + + + \ No newline at end of file diff --git a/Source/MQTTnet.Server/wwwroot/app/app.ts b/Source/MQTTnet.Server/wwwroot/app/app.ts new file mode 100644 index 00000000..b7167701 --- /dev/null +++ b/Source/MQTTnet.Server/wwwroot/app/app.ts @@ -0,0 +1,58 @@ +import { connect } from "mqtt"; + +var client = connect('ws://' + location.host + '/mqtt', + { + clientId: "client" + Math.floor(Math.random() * 6) + 1 + }); + +window.onbeforeunload = () => { + client.end(); +}; + +var publishButton = document.getElementById("publish"); +var topicInput = document.getElementById("topic"); +var msgInput = document.getElementById("msg"); +var stateParagraph = document.getElementById("state"); +var msgsList = document.getElementById("msgs"); + +publishButton.onclick = click => { + var topic = topicInput.value; + var msg = msgInput.value; + client.publish(topic, msg); +}; + +client.on('connect', () => { + client.subscribe('#', { qos: 0 }, (err, granted) => { + console.log(err); + }); + client.publish('presence', 'Hello mqtt'); + + stateParagraph.innerText = "connected"; + showMsg("[connect]"); +}); + +client.on("error", e => { + showMsg("error: " + e.message); +}); + +client.on("reconnect", () => { + stateParagraph.innerText = "reconnecting"; + showMsg("[reconnect]"); +}); + +client.on('message', (topic, message) => { + showMsg(topic + ": " + message.toString()); +}); + +function showMsg(msg: string) { + //console.log(msg); + + var node = document.createElement("LI"); + node.appendChild(document.createTextNode(msg)); + + msgsList.appendChild(node); + + if (msgsList.childElementCount > 50) { + msgsList.removeChild(msgsList.childNodes[0]); + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs index f4993224..e759221f 100644 --- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs @@ -10,6 +10,8 @@ namespace MQTTnet.Adapter { string Endpoint { get; } + bool IsSecureConnection { get; } + MqttPacketFormatterAdapter PacketFormatterAdapter { get; } event EventHandler ReadingPacketStarted; diff --git a/Source/MQTTnet/Adapter/IMqttServerAdapter.cs b/Source/MQTTnet/Adapter/IMqttServerAdapter.cs index eff9eab7..2e0bfd1b 100644 --- a/Source/MQTTnet/Adapter/IMqttServerAdapter.cs +++ b/Source/MQTTnet/Adapter/IMqttServerAdapter.cs @@ -6,7 +6,7 @@ namespace MQTTnet.Adapter { public interface IMqttServerAdapter : IDisposable { - event EventHandler ClientAccepted; + Action ClientAcceptedHandler { get; set; } Task StartAsync(IMqttServerOptions options); Task StopAsync(); diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 1bfcff02..eba18f7d 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -42,6 +42,8 @@ namespace MQTTnet.Adapter public string Endpoint => _channel.Endpoint; + public bool IsSecureConnection => _channel.IsSecureConnection; + public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public event EventHandler ReadingPacketStarted; diff --git a/Source/MQTTnet/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs index 4dcb6684..4848b46d 100644 --- a/Source/MQTTnet/Channel/IMqttChannel.cs +++ b/Source/MQTTnet/Channel/IMqttChannel.cs @@ -7,6 +7,7 @@ namespace MQTTnet.Channel public interface IMqttChannel : IDisposable { string Endpoint { get; } + bool IsSecureConnection { get; } Task ConnectAsync(CancellationToken cancellationToken); Task DisconnectAsync(CancellationToken cancellationToken); diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs index 4e25e9c5..70259cf6 100644 --- a/Source/MQTTnet/Client/IMqttClient.cs +++ b/Source/MQTTnet/Client/IMqttClient.cs @@ -15,13 +15,9 @@ namespace MQTTnet.Client IMqttClientOptions Options { get; } IMqttClientConnectedHandler ConnectedHandler { get; set; } - [Obsolete("Use ConnectedHandler instead.")] - event EventHandler Connected; - + IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } - [Obsolete("Use DisconnectedHandler instead.")] - event EventHandler Disconnected; - + Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken); Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index d431ba5c..a09f6b53 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -46,15 +46,9 @@ namespace MQTTnet.Client public IMqttClientConnectedHandler ConnectedHandler { get; set; } - public event EventHandler Connected; - public IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } - public event EventHandler Disconnected; - - public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } - - public event EventHandler ApplicationMessageReceived; + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; } public bool IsConnected { get; private set; } @@ -106,9 +100,6 @@ namespace MQTTnet.Client await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(authenticateResult)).ConfigureAwait(false); } - // TODO: Remove! - Connected?.Invoke(this, new MqttClientConnectedEventArgs(authenticateResult)); - return authenticateResult; } catch (Exception exception) @@ -276,8 +267,6 @@ namespace MQTTnet.Client { await disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception)).ConfigureAwait(false); } - - Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); } } @@ -521,25 +510,27 @@ namespace MQTTnet.Client } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) { - await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); - - await SendAsync(new MqttPubAckPacket + if (await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false)) { - PacketIdentifier = publishPacket.PacketIdentifier, - ReasonCode = MqttPubAckReasonCode.Success - }, cancellationToken).ConfigureAwait(false); + await SendAsync(new MqttPubAckPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubAckReasonCode.Success + }, cancellationToken).ConfigureAwait(false); + } } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) { - await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); - - var pubRecPacket = new MqttPubRecPacket + if (await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false)) { - PacketIdentifier = publishPacket.PacketIdentifier, - ReasonCode = MqttPubRecReasonCode.Success - }; + var pubRecPacket = new MqttPubRecPacket + { + PacketIdentifier = publishPacket.PacketIdentifier, + ReasonCode = MqttPubRecReasonCode.Success + }; - await SendAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); + await SendAsync(pubRecPacket, cancellationToken).ConfigureAwait(false); + } } else { @@ -583,20 +574,19 @@ namespace MQTTnet.Client return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(pubRecPacket, pubCompPacket); } - private Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket) + private async Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket) { var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket); - ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage)); - - var handler = ReceivedApplicationMessageHandler; + var handler = ApplicationMessageReceivedHandler; if (handler != null) { - return handler.HandleApplicationMessageAsync( - new MqttApplicationMessageHandlerContext(Options.ClientId, applicationMessage)); + var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage); + await handler.HandleApplicationMessageReceivedAsync(eventArgs).ConfigureAwait(false); + return !eventArgs.ProcessingFailed; } - return Task.FromResult(0); + return true; } private static async Task WaitForTaskAsync(Task task, Task sender) diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index eacdbf4d..a44eb42a 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -63,35 +63,35 @@ namespace MQTTnet.Client return client; } - public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, Func handler) + public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, Func handler) { if (handler == null) { - client.ReceivedApplicationMessageHandler = null; + client.ApplicationMessageReceivedHandler = null; return client; } - client.ReceivedApplicationMessageHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } - public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, Action handler) + public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, Action handler) { if (handler == null) { - client.ReceivedApplicationMessageHandler = null; + client.ApplicationMessageReceivedHandler = null; return client; } - client.ReceivedApplicationMessageHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } - public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, IMqttApplicationMessageHandler handler) + public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, IMqttApplicationMessageReceivedHandler handler) { - client.ReceivedApplicationMessageHandler = handler; + client.ApplicationMessageReceivedHandler = handler; return client; } diff --git a/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs deleted file mode 100644 index fdc5bf6a..00000000 --- a/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace MQTTnet.Client.Receiving -{ - public interface IMqttApplicationMessageHandler - { - Task HandleApplicationMessageAsync(MqttApplicationMessageHandlerContext context); - } -} diff --git a/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageReceivedHandler.cs b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageReceivedHandler.cs new file mode 100644 index 00000000..3ea73175 --- /dev/null +++ b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageReceivedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Client.Receiving +{ + public interface IMqttApplicationMessageReceivedHandler + { + Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs deleted file mode 100644 index 87fb959f..00000000 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MQTTnet.Client.Receiving -{ - public class MqttApplicationMessageHandlerContext - { - public MqttApplicationMessageHandlerContext(string senderClientId, MqttApplicationMessage applicationMessage) - { - SenderClientId = senderClientId; - ApplicationMessage = applicationMessage; - } - - public string SenderClientId { get; } - - public MqttApplicationMessage ApplicationMessage { get; } - } -} diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs deleted file mode 100644 index cde89443..00000000 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace MQTTnet.Client.Receiving -{ - public class MqttApplicationMessageHandlerDelegate : IMqttApplicationMessageHandler - { - private readonly Func _handler; - - public MqttApplicationMessageHandlerDelegate(Action handler) - { - if (handler == null) throw new ArgumentNullException(nameof(handler)); - - _handler = context => - { - handler(context); - return Task.FromResult(0); - }; - } - - public MqttApplicationMessageHandlerDelegate(Func handler) - { - _handler = handler ?? throw new ArgumentNullException(nameof(handler)); - } - - public Task HandleApplicationMessageAsync(MqttApplicationMessageHandlerContext context) - { - return _handler(context); - } - } -} diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs new file mode 100644 index 00000000..1d73aaa1 --- /dev/null +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Client.Receiving +{ + public class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler + { + private readonly Func _handler; + + public MqttApplicationMessageReceivedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = context => + { + handler(context); + return Task.FromResult(0); + }; + } + + public MqttApplicationMessageReceivedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs context) + { + return _handler(context); + } + } +} diff --git a/Source/MQTTnet/IApplicationMessageReceiver.cs b/Source/MQTTnet/IApplicationMessageReceiver.cs index 806bb757..4e474856 100644 --- a/Source/MQTTnet/IApplicationMessageReceiver.cs +++ b/Source/MQTTnet/IApplicationMessageReceiver.cs @@ -1,13 +1,9 @@ -using System; -using MQTTnet.Client.Receiving; +using MQTTnet.Client.Receiving; namespace MQTTnet { public interface IApplicationMessageReceiver { - IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } - - [Obsolete("Use _ReceivedApplicationMessageHandler_ instead.")] - event EventHandler ApplicationMessageReceived; + IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; } } } diff --git a/Source/MQTTnet/IMqttFactory.cs b/Source/MQTTnet/IMqttFactory.cs new file mode 100644 index 00000000..487d77f4 --- /dev/null +++ b/Source/MQTTnet/IMqttFactory.cs @@ -0,0 +1,11 @@ +using MQTTnet.Client; +using MQTTnet.Diagnostics; +using MQTTnet.Server; + +namespace MQTTnet +{ + public interface IMqttFactory : IMqttClientFactory, IMqttServerFactory + { + IMqttNetLogger DefaultLogger { get; } + } +} diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 0f5df6f5..2377cde7 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -1,6 +1,5 @@ using System; using MQTTnet.Adapter; -using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Diagnostics; using MQTTnet.Formatter; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index ea5d6ccf..092b7030 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -36,6 +36,8 @@ namespace MQTTnet.Implementations _bufferSize = serverOptions.DefaultEndpointOptions.BufferSize; CreateStreams(); + + IsSecureConnection = socket.Information.ProtectionLevel >= SocketProtectionLevel.Tls12; } public static Func> CustomIgnorableServerCertificateErrorsResolver { get; set; } @@ -53,6 +55,8 @@ namespace MQTTnet.Implementations } } + public bool IsSecureConnection { get; } + public async Task ConnectAsync(CancellationToken cancellationToken) { if (_socket == null) diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 4a178855..e6b7debc 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -27,6 +27,8 @@ namespace MQTTnet.Implementations { _clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); _options = (MqttClientTcpOptions)clientOptions.ChannelOptions; + + IsSecureConnection = clientOptions.ChannelOptions?.TlsOptions?.UseTls == true; } /// @@ -37,6 +39,8 @@ namespace MQTTnet.Implementations { _socket = socket ?? throw new ArgumentNullException(nameof(socket)); + IsSecureConnection = sslStream != null; + CreateStream(sslStream); } @@ -45,6 +49,8 @@ namespace MQTTnet.Implementations public string Endpoint => _socket?.RemoteEndPoint?.ToString(); + public bool IsSecureConnection { get; } + public async Task ConnectAsync(CancellationToken cancellationToken) { if (_socket == null) diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs index dd93b144..9fbf27b8 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.Uwp.cs @@ -23,7 +23,7 @@ namespace MQTTnet.Implementations _logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter)); } - public event EventHandler ClientAccepted; + public Action ClientAcceptedHandler { get; set; } public async Task StartAsync(IMqttServerOptions options) { @@ -73,7 +73,7 @@ namespace MQTTnet.Implementations try { var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket, _options), new MqttPacketFormatterAdapter(), _logger); - ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); + ClientAcceptedHandler?.Invoke(new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (Exception exception) { diff --git a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs index cfbf7251..92ccb81d 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerAdapter.cs @@ -26,7 +26,7 @@ namespace MQTTnet.Implementations _logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter)); } - public event EventHandler ClientAccepted; + public Action ClientAcceptedHandler { get; set; } public Task StartAsync(IMqttServerOptions options) { @@ -87,9 +87,11 @@ namespace MQTTnet.Implementations options, tlsCertificate, _cancellationTokenSource.Token, - _logger); + _logger) + { + ClientAcceptedHandler = OnClientAccepted + }; - listenerV4.ClientAccepted += OnClientAccepted; listenerV4.Start(); _listeners.Add(listenerV4); } @@ -101,17 +103,19 @@ namespace MQTTnet.Implementations options, tlsCertificate, _cancellationTokenSource.Token, - _logger); + _logger) + { + ClientAcceptedHandler = OnClientAccepted + }; - listenerV6.ClientAccepted += OnClientAccepted; listenerV6.Start(); _listeners.Add(listenerV6); } } - private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs e) + private void OnClientAccepted(MqttServerAdapterClientAcceptedEventArgs eventArgs) { - ClientAccepted?.Invoke(this, e); + ClientAcceptedHandler?.Invoke(eventArgs); } } } diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs index c550ebb0..f1207b2d 100644 --- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs +++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs @@ -42,7 +42,7 @@ namespace MQTTnet.Implementations } } - public event EventHandler ClientAccepted; + public Action ClientAcceptedHandler; public void Start() { @@ -88,7 +88,7 @@ namespace MQTTnet.Implementations _addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6"); var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketFormatterAdapter(), _logger); - ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); + ClientAcceptedHandler?.Invoke(new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); } catch (ObjectDisposedException) { diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index 6240f25f..59329a5a 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Net; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; @@ -21,14 +22,18 @@ namespace MQTTnet.Implementations _options = options ?? throw new ArgumentNullException(nameof(options)); } - public MqttWebSocketChannel(WebSocket webSocket, string endpoint) + public MqttWebSocketChannel(WebSocket webSocket, string endpoint, bool isSecureConnection) { _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); + Endpoint = endpoint; + IsSecureConnection = isSecureConnection; } public string Endpoint { get; } + public bool IsSecureConnection { get; private set; } + public async Task ConnectAsync(CancellationToken cancellationToken) { var uri = _options.Uri; @@ -83,6 +88,8 @@ namespace MQTTnet.Implementations await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false); _webSocket = clientWebSocket; + + IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase); } public async Task DisconnectAsync(CancellationToken cancellationToken) diff --git a/Source/MQTTnet/Internal/TestMqttChannel.cs b/Source/MQTTnet/Internal/TestMqttChannel.cs index 03f23b14..1ef8e5a5 100644 --- a/Source/MQTTnet/Internal/TestMqttChannel.cs +++ b/Source/MQTTnet/Internal/TestMqttChannel.cs @@ -16,6 +16,8 @@ namespace MQTTnet.Internal public string Endpoint { get; } = ""; + public bool IsSecureConnection { get; } = false; + public Task ConnectAsync(CancellationToken cancellationToken) { return Task.FromResult(0); diff --git a/Source/MQTTnet/MqttApplicationMessageReceivedEventArgs.cs b/Source/MQTTnet/MqttApplicationMessageReceivedEventArgs.cs index e81c69a9..6fbaa933 100644 --- a/Source/MQTTnet/MqttApplicationMessageReceivedEventArgs.cs +++ b/Source/MQTTnet/MqttApplicationMessageReceivedEventArgs.cs @@ -13,5 +13,7 @@ namespace MQTTnet public string ClientId { get; } public MqttApplicationMessage ApplicationMessage { get; } + + public bool ProcessingFailed { get; set; } } } diff --git a/Source/MQTTnet/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs index 3f1e1acb..ab6cc760 100644 --- a/Source/MQTTnet/MqttFactory.cs +++ b/Source/MQTTnet/MqttFactory.cs @@ -8,11 +8,22 @@ using MQTTnet.Server; namespace MQTTnet { - public class MqttFactory : IMqttClientFactory, IMqttServerFactory + public class MqttFactory : IMqttFactory { + public MqttFactory() : this(new MqttNetLogger()) + { + } + + public MqttFactory(IMqttNetLogger logger) + { + DefaultLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public IMqttNetLogger DefaultLogger { get; } + public IMqttClient CreateMqttClient() { - return CreateMqttClient(new MqttNetLogger()); + return CreateMqttClient(DefaultLogger); } public IMqttClient CreateMqttClient(IMqttNetLogger logger) @@ -26,7 +37,7 @@ namespace MQTTnet { if (adapterFactory == null) throw new ArgumentNullException(nameof(adapterFactory)); - return new MqttClient(adapterFactory, new MqttNetLogger()); + return new MqttClient(adapterFactory, DefaultLogger); } public IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory adapterFactory) @@ -39,8 +50,7 @@ namespace MQTTnet public IMqttServer CreateMqttServer() { - var logger = new MqttNetLogger(); - return CreateMqttServer(logger); + return CreateMqttServer(DefaultLogger); } public IMqttServer CreateMqttServer(IMqttNetLogger logger) @@ -57,5 +67,12 @@ namespace MQTTnet return new MqttServer(adapters, logger.CreateChildLogger()); } + + public IMqttServer CreateMqttServer(IEnumerable adapters) + { + if (adapters == null) throw new ArgumentNullException(nameof(adapters)); + + return new MqttServer(adapters, DefaultLogger.CreateChildLogger()); + } } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index 8c7ae464..18893977 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -27,7 +27,13 @@ public override string ToString() { - return string.Concat("Connect: [ProtocolLevel=", ProtocolLevel, "] [ClientId=", ClientId, "] [Username=", Username, "] [Password=", Password, "] [KeepAlivePeriod=", KeepAlivePeriod, "] [CleanSession=", CleanSession, "]"); + var password = Password; + if (!string.IsNullOrEmpty(password)) + { + password = "****"; + } + + return string.Concat("Connect: [ProtocolLevel=", ProtocolLevel, "] [ClientId=", ClientId, "] [Username=", Username, "] [Password=", password, "] [KeepAlivePeriod=", KeepAlivePeriod, "] [CleanSession=", CleanSession, "]"); } } } diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index aceceeee..cf1681dd 100644 --- a/Source/MQTTnet/Server/IMqttServer.cs +++ b/Source/MQTTnet/Server/IMqttServer.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using MQTTnet.Server.Status; @@ -7,13 +6,13 @@ namespace MQTTnet.Server { public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher { - event EventHandler Started; - event EventHandler Stopped; + IMqttServerStartedHandler StartedHandler { get; set; } + IMqttServerStoppedHandler StoppedHandler { get; set; } - event EventHandler ClientConnected; - event EventHandler ClientDisconnected; - event EventHandler ClientSubscribedTopic; - event EventHandler ClientUnsubscribedTopic; + IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } + IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } + IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get; set; } + IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get; set; } IMqttServerOptions Options { get; } diff --git a/Source/MQTTnet/Server/IMqttServerClientConnectedHandler.cs b/Source/MQTTnet/Server/IMqttServerClientConnectedHandler.cs new file mode 100644 index 00000000..b507e41b --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerClientConnectedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerClientConnectedHandler + { + Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerClientDisconnectedHandler.cs b/Source/MQTTnet/Server/IMqttServerClientDisconnectedHandler.cs new file mode 100644 index 00000000..3614d2d2 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerClientDisconnectedHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerClientDisconnectedHandler + { + Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerClientSubscribedTopicHandler.cs b/Source/MQTTnet/Server/IMqttServerClientSubscribedTopicHandler.cs new file mode 100644 index 00000000..6f82bb36 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerClientSubscribedTopicHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerClientSubscribedTopicHandler + { + Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerClientUnsubscribedTopicHandler.cs b/Source/MQTTnet/Server/IMqttServerClientUnsubscribedTopicHandler.cs new file mode 100644 index 00000000..077a80f9 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerClientUnsubscribedTopicHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerClientUnsubscribedTopicHandler + { + Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerFactory.cs b/Source/MQTTnet/Server/IMqttServerFactory.cs index 7d23ed36..e8c7d401 100644 --- a/Source/MQTTnet/Server/IMqttServerFactory.cs +++ b/Source/MQTTnet/Server/IMqttServerFactory.cs @@ -10,6 +10,8 @@ namespace MQTTnet.Server IMqttServer CreateMqttServer(IMqttNetLogger logger); + IMqttServer CreateMqttServer(IEnumerable adapters); + IMqttServer CreateMqttServer(IEnumerable adapters, IMqttNetLogger logger); } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/IMqttServerStartedHandler.cs b/Source/MQTTnet/Server/IMqttServerStartedHandler.cs new file mode 100644 index 00000000..e6155f14 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerStartedHandler.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerStartedHandler + { + Task HandleServerStartedAsync(EventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Server/IMqttServerStoppedHandler.cs b/Source/MQTTnet/Server/IMqttServerStoppedHandler.cs new file mode 100644 index 00000000..34df3236 --- /dev/null +++ b/Source/MQTTnet/Server/IMqttServerStoppedHandler.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public interface IMqttServerStoppedHandler + { + Task HandleServerStoppedAsync(EventArgs eventArgs); + } +} diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index fa1d6a05..4981b06a 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -14,62 +14,6 @@ using MQTTnet.Server.Status; namespace MQTTnet.Server { - public class MqttClientSession - { - private readonly DateTime _createdTimestamp = DateTime.UtcNow; - - public MqttClientSession(string clientId, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions) - { - ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); - - SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, eventDispatcher, serverOptions); - ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions); - } - - public string ClientId { get; } - - public bool IsCleanSession { get; set; } = true; - - public MqttApplicationMessage WillMessage { get; set; } - - public MqttClientSubscriptionsManager SubscriptionsManager { get; } - - public MqttClientSessionApplicationMessagesQueue ApplicationMessagesQueue { get; } - - public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage) - { - var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel); - if (!checkSubscriptionsResult.IsSubscribed) - { - return; - } - - ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage); - } - - public async Task SubscribeAsync(ICollection topicFilters, MqttRetainedMessagesManager retainedMessagesManager) - { - await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false); - var matchingRetainedMessages = await retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); - foreach (var matchingRetainedMessage in matchingRetainedMessages) - { - EnqueueApplicationMessage(matchingRetainedMessage, null, true); - } - } - - public Task UnsubscribeAsync(IEnumerable topicFilters) - { - return SubscriptionsManager.UnsubscribeAsync(topicFilters); - } - - public void FillStatus(MqttSessionStatus status) - { - status.ClientId = ClientId; - status.CreatedTimestamp = _createdTimestamp; - status.PendingApplicationMessagesCount = ApplicationMessagesQueue.Count; - } - } - public class MqttClientConnection : IMqttClientSession, IDisposable { private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); @@ -425,7 +369,7 @@ namespace MQTTnet.Server //// } ////}); - await Task.FromResult(0); + //await Task.FromResult(0); } private async Task SendPendingPacketsAsync() diff --git a/Source/MQTTnet/Server/MqttClientSession.cs b/Source/MQTTnet/Server/MqttClientSession.cs new file mode 100644 index 00000000..427fdfa0 --- /dev/null +++ b/Source/MQTTnet/Server/MqttClientSession.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MQTTnet.Server.Status; + +namespace MQTTnet.Server +{ + public class MqttClientSession + { + private readonly DateTime _createdTimestamp = DateTime.UtcNow; + + public MqttClientSession(string clientId, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions) + { + ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); + + SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, eventDispatcher, serverOptions); + ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions); + } + + public string ClientId { get; } + + public bool IsCleanSession { get; set; } = true; + + public MqttApplicationMessage WillMessage { get; set; } + + public MqttClientSubscriptionsManager SubscriptionsManager { get; } + + public MqttClientSessionApplicationMessagesQueue ApplicationMessagesQueue { get; } + + public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage) + { + var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel); + if (!checkSubscriptionsResult.IsSubscribed) + { + return; + } + + ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage); + } + + public async Task SubscribeAsync(ICollection topicFilters, MqttRetainedMessagesManager retainedMessagesManager) + { + await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false); + var matchingRetainedMessages = await retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); + foreach (var matchingRetainedMessage in matchingRetainedMessages) + { + EnqueueApplicationMessage(matchingRetainedMessage, null, true); + } + } + + public Task UnsubscribeAsync(IEnumerable topicFilters) + { + return SubscriptionsManager.UnsubscribeAsync(topicFilters); + } + + public void FillStatus(MqttSessionStatus status) + { + status.ClientId = ClientId; + status.CreatedTimestamp = _createdTimestamp; + status.PendingApplicationMessagesCount = ApplicationMessagesQueue.Count; + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index e0ce7e4e..07640d94 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -15,6 +15,7 @@ namespace MQTTnet.Server { private readonly BlockingCollection _messageQueue = new BlockingCollection(); + private readonly SemaphoreSlim _createConnectionGate = new SemaphoreSlim(1, 1); private readonly ConcurrentDictionary _connections = new ConcurrentDictionary(); private readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); @@ -49,8 +50,6 @@ namespace MQTTnet.Server public async Task StopAsync() { - //using (await _sessionsLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) - foreach (var connection in _connections.Values) { await connection.StopAsync().ConfigureAwait(false); @@ -133,7 +132,7 @@ namespace MQTTnet.Server { if (_connections.TryGetValue(clientId, out var connection)) { - await connection.StopAsync(); + await connection.StopAsync().ConfigureAwait(false); } if (_sessions.TryRemove(clientId, out var session)) @@ -196,7 +195,7 @@ namespace MQTTnet.Server applicationMessage = interceptorContext.ApplicationMessage; } - _eventDispatcher.OnApplicationMessageReceived(sender?.ClientId, applicationMessage); + await _eventDispatcher.HandleApplicationMessageReceivedAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false); if (applicationMessage.Retain) { @@ -258,7 +257,7 @@ namespace MQTTnet.Server var connection = await CreateConnectionAsync(channelAdapter, connectPacket).ConfigureAwait(false); - _eventDispatcher.OnClientConnected(clientId); + await _eventDispatcher.HandleClientConnectedAsync(clientId).ConfigureAwait(false); disconnectType = await connection.RunAsync().ConfigureAwait(false); } @@ -288,7 +287,7 @@ namespace MQTTnet.Server await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false); - _eventDispatcher.OnClientDisconnected(clientId, disconnectType); + await _eventDispatcher.HandleClientDisconnectedAsync(clientId, disconnectType).ConfigureAwait(false); } } @@ -299,7 +298,8 @@ namespace MQTTnet.Server connectPacket.Username, connectPacket.Password, connectPacket.WillMessage, - clientAdapter.Endpoint); + clientAdapter.Endpoint, + clientAdapter.IsSecureConnection); var connectionValidator = _options.ConnectionValidator; @@ -313,8 +313,6 @@ namespace MQTTnet.Server return context; } - private readonly SemaphoreSlim _createConnectionGate = new SemaphoreSlim(1, 1); - private async Task CreateConnectionAsync(IMqttChannelAdapter channelAdapter, MqttConnectPacket connectPacket) { await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false); diff --git a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs index d4c2eead..904474f5 100644 --- a/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSubscriptionsManager.cs @@ -61,7 +61,7 @@ namespace MQTTnet.Server _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; } - _eventDispatcher.OnClientSubscribedTopic(_clientId, topicFilter); + await _eventDispatcher.HandleClientSubscribedTopicAsync(_clientId, topicFilter).ConfigureAwait(false); } } @@ -85,12 +85,12 @@ namespace MQTTnet.Server _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; } - _eventDispatcher.OnClientSubscribedTopic(_clientId, topicFilter); + await _eventDispatcher.HandleClientSubscribedTopicAsync(_clientId, topicFilter).ConfigureAwait(false); } } } - public Task UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) + public async Task UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) { if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); @@ -111,12 +111,15 @@ namespace MQTTnet.Server { unsubAckPacket.ReasonCodes.Add(MqttUnsubscribeReasonCode.NoSubscriptionExisted); } - - _eventDispatcher.OnClientUnsubscribedTopic(_clientId, topicFilter); } } - return Task.FromResult(unsubAckPacket); + foreach (var topicFilter in unsubscribePacket.TopicFilters) + { + await _eventDispatcher.HandleClientUnsubscribedTopicAsync(_clientId, topicFilter).ConfigureAwait(false); + } + + return unsubAckPacket; } public Task UnsubscribeAsync(IEnumerable topicFilters) diff --git a/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs b/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs index 5857862b..b06eb844 100644 --- a/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs +++ b/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs @@ -4,13 +4,14 @@ namespace MQTTnet.Server { public class MqttConnectionValidatorContext { - public MqttConnectionValidatorContext(string clientId, string username, string password, MqttApplicationMessage willMessage, string endpoint) + public MqttConnectionValidatorContext(string clientId, string username, string password, MqttApplicationMessage willMessage, string endpoint, bool isSecureConnection) { ClientId = clientId; Username = username; Password = password; WillMessage = willMessage; Endpoint = endpoint; + IsSecureConnection = isSecureConnection; } public string ClientId { get; } @@ -23,6 +24,8 @@ namespace MQTTnet.Server public string Endpoint { get; } + public bool IsSecureConnection { get; } + public MqttConnectReturnCode ReturnCode { get; set; } = MqttConnectReturnCode.ConnectionAccepted; } } diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 4d36e4cf..c3b970c4 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -28,35 +28,41 @@ namespace MQTTnet.Server if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateChildLogger(nameof(MqttServer)); - - _eventDispatcher.ClientConnected += (s, e) => ClientConnected?.Invoke(s, e); - _eventDispatcher.ClientDisconnected += (s, e) => ClientDisconnected?.Invoke(s, e); - _eventDispatcher.ClientSubscribedTopic += (s, e) => ClientSubscribedTopic?.Invoke(s, e); - _eventDispatcher.ClientUnsubscribedTopic += (s, e) => ClientUnsubscribedTopic?.Invoke(s, e); - _eventDispatcher.ApplicationMessageReceived += async (s, e) => - { - // TODO: Migrate EventDispatcher to proper handlers and no events anymore. - ApplicationMessageReceived?.Invoke(s, e); - - var handler = ReceivedApplicationMessageHandler; - if (handler != null) - { - await handler.HandleApplicationMessageAsync( - new MqttApplicationMessageHandlerContext(e.ClientId, e.ApplicationMessage)).ConfigureAwait(false); - } - }; } - public event EventHandler Started; - public event EventHandler Stopped; + public IMqttServerStartedHandler StartedHandler { get; set; } - public event EventHandler ClientConnected; - public event EventHandler ClientDisconnected; - public event EventHandler ClientSubscribedTopic; - public event EventHandler ClientUnsubscribedTopic; + public IMqttServerStoppedHandler StoppedHandler { get; set; } - public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } - public event EventHandler ApplicationMessageReceived; + public IMqttServerClientConnectedHandler ClientConnectedHandler + { + get => _eventDispatcher.ClientConnectedHandler; + set => _eventDispatcher.ClientConnectedHandler = value; + } + + public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler + { + get => _eventDispatcher.ClientDisconnectedHandler; + set => _eventDispatcher.ClientDisconnectedHandler = value; + } + + public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler + { + get => _eventDispatcher.ClientSubscribedTopicHandler; + set => _eventDispatcher.ClientSubscribedTopicHandler = value; + } + + public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler + { + get => _eventDispatcher.ClientUnsubscribedTopicHandler; + set => _eventDispatcher.ClientUnsubscribedTopicHandler = value; + } + + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler + { + get => _eventDispatcher.ApplicationMessageReceivedHandler; + set => _eventDispatcher.ApplicationMessageReceivedHandler = value; + } public IMqttServerOptions Options { get; private set; } @@ -118,12 +124,17 @@ namespace MQTTnet.Server foreach (var adapter in _adapters) { - adapter.ClientAccepted += OnClientAccepted; + adapter.ClientAcceptedHandler = OnClientAccepted; await adapter.StartAsync(Options).ConfigureAwait(false); } _logger.Info("Started."); - Started?.Invoke(this, EventArgs.Empty); + + var startedHandler = StartedHandler; + if (startedHandler != null) + { + await startedHandler.HandleServerStartedAsync(EventArgs.Empty).ConfigureAwait(false); + } } public async Task StopAsync() @@ -141,12 +152,11 @@ namespace MQTTnet.Server foreach (var adapter in _adapters) { - adapter.ClientAccepted -= OnClientAccepted; + adapter.ClientAcceptedHandler = null; await adapter.StopAsync().ConfigureAwait(false); } _logger.Info("Stopped."); - Stopped?.Invoke(this, EventArgs.Empty); } finally { @@ -158,6 +168,12 @@ namespace MQTTnet.Server _clientSessionsManager?.Dispose(); _clientSessionsManager = null; } + + var stoppedHandler = StoppedHandler; + if (stoppedHandler != null) + { + await stoppedHandler.HandleServerStoppedAsync(EventArgs.Empty).ConfigureAwait(false); + } } public Task ClearRetainedMessagesAsync() @@ -165,7 +181,7 @@ namespace MQTTnet.Server return _retainedMessagesManager?.ClearMessagesAsync(); } - private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) + private void OnClientAccepted(MqttServerAdapterClientAcceptedEventArgs eventArgs) { eventArgs.SessionTask = _clientSessionsManager.HandleConnectionAsync(eventArgs.Client); } diff --git a/Source/MQTTnet/Server/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Server/MqttServerClientConnectedEventArgs.cs similarity index 61% rename from Source/MQTTnet/Server/MqttClientConnectedEventArgs.cs rename to Source/MQTTnet/Server/MqttServerClientConnectedEventArgs.cs index 6f043b44..71c42c3e 100644 --- a/Source/MQTTnet/Server/MqttClientConnectedEventArgs.cs +++ b/Source/MQTTnet/Server/MqttServerClientConnectedEventArgs.cs @@ -2,9 +2,9 @@ namespace MQTTnet.Server { - public class MqttClientConnectedEventArgs : EventArgs + public class MqttServerClientConnectedEventArgs : EventArgs { - public MqttClientConnectedEventArgs(string clientId) + public MqttServerClientConnectedEventArgs(string clientId) { ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); } diff --git a/Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs new file mode 100644 index 00000000..2883d66b --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerClientConnectedHandlerDelegate : IMqttServerClientConnectedHandler + { + private readonly Func _handler; + + public MqttServerClientConnectedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public MqttServerClientConnectedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Server/MqttServerClientDisconnectedEventArgs.cs similarity index 64% rename from Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs rename to Source/MQTTnet/Server/MqttServerClientDisconnectedEventArgs.cs index 225d0250..9e742749 100644 --- a/Source/MQTTnet/Server/MqttClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet/Server/MqttServerClientDisconnectedEventArgs.cs @@ -2,9 +2,9 @@ namespace MQTTnet.Server { - public class MqttClientDisconnectedEventArgs : EventArgs + public class MqttServerClientDisconnectedEventArgs : EventArgs { - public MqttClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType) + public MqttServerClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType) { ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); DisconnectType = disconnectType; diff --git a/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs new file mode 100644 index 00000000..b1d495a6 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerClientDisconnectedHandlerDelegate : IMqttServerClientDisconnectedHandler + { + private readonly Func _handler; + + public MqttServerClientDisconnectedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public MqttServerClientDisconnectedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet/Server/MqttClientSubscribedTopicEventArgs.cs b/Source/MQTTnet/Server/MqttServerClientSubscribedTopicEventArgs.cs similarity index 67% rename from Source/MQTTnet/Server/MqttClientSubscribedTopicEventArgs.cs rename to Source/MQTTnet/Server/MqttServerClientSubscribedTopicEventArgs.cs index 11f5c063..1454ac94 100644 --- a/Source/MQTTnet/Server/MqttClientSubscribedTopicEventArgs.cs +++ b/Source/MQTTnet/Server/MqttServerClientSubscribedTopicEventArgs.cs @@ -2,9 +2,9 @@ namespace MQTTnet.Server { - public class MqttClientSubscribedTopicEventArgs : EventArgs + public class MqttServerClientSubscribedTopicEventArgs : EventArgs { - public MqttClientSubscribedTopicEventArgs(string clientId, TopicFilter topicFilter) + public MqttServerClientSubscribedTopicEventArgs(string clientId, TopicFilter topicFilter) { ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); diff --git a/Source/MQTTnet/Server/MqttServerClientSubscribedTopicHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerClientSubscribedTopicHandlerDelegate.cs new file mode 100644 index 00000000..048b0a8a --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerClientSubscribedTopicHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerClientSubscribedHandlerDelegate : IMqttServerClientSubscribedTopicHandler + { + private readonly Func _handler; + + public MqttServerClientSubscribedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public MqttServerClientSubscribedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet/Server/MqttClientUnSubscribedTopicEventArgs.cs b/Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicEventArgs.cs similarity index 67% rename from Source/MQTTnet/Server/MqttClientUnSubscribedTopicEventArgs.cs rename to Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicEventArgs.cs index d76347e0..bb78bb28 100644 --- a/Source/MQTTnet/Server/MqttClientUnSubscribedTopicEventArgs.cs +++ b/Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicEventArgs.cs @@ -2,9 +2,9 @@ namespace MQTTnet.Server { - public class MqttClientUnsubscribedTopicEventArgs : EventArgs + public class MqttServerClientUnsubscribedTopicEventArgs : EventArgs { - public MqttClientUnsubscribedTopicEventArgs(string clientId, string topicFilter) + public MqttServerClientUnsubscribedTopicEventArgs(string clientId, string topicFilter) { ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); diff --git a/Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicHandlerDelegate.cs new file mode 100644 index 00000000..44162876 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerClientUnsubscribedTopicHandlerDelegate : IMqttServerClientUnsubscribedTopicHandler + { + private readonly Func _handler; + + public MqttServerClientUnsubscribedTopicHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public MqttServerClientUnsubscribedTopicHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index 03ed93d5..c85805d8 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -1,42 +1,73 @@ -using System; +using System.Threading.Tasks; +using MQTTnet.Client.Receiving; namespace MQTTnet.Server { public class MqttServerEventDispatcher { - public event EventHandler ClientSubscribedTopic; + public IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } - public event EventHandler ClientUnsubscribedTopic; + public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } - public event EventHandler ClientConnected; + public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get; set; } - public event EventHandler ClientDisconnected; + public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get; set; } - public event EventHandler ApplicationMessageReceived; + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; } - public void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) + public Task HandleClientConnectedAsync(string clientId) { - ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter)); + var handler = ClientConnectedHandler; + if (handler == null) + { + return Task.FromResult(0); + } + + return handler.HandleClientConnectedAsync(new MqttServerClientConnectedEventArgs(clientId)); } - public void OnClientUnsubscribedTopic(string clientId, string topicFilter) + public Task HandleClientDisconnectedAsync(string clientId, MqttClientDisconnectType disconnectType) { - ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter)); + var handler = ClientDisconnectedHandler; + if (handler == null) + { + return Task.FromResult(0); + } + + return handler.HandleClientDisconnectedAsync(new MqttServerClientDisconnectedEventArgs(clientId, disconnectType)); } - public void OnClientDisconnected(string clientId, MqttClientDisconnectType disconnectType) + public Task HandleClientSubscribedTopicAsync(string clientId, TopicFilter topicFilter) { - ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, disconnectType)); + var handler = ClientSubscribedTopicHandler; + if (handler == null) + { + return Task.FromResult(0); + } + + return handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs(clientId, topicFilter)); } - public void OnApplicationMessageReceived(string senderClientId, MqttApplicationMessage applicationMessage) + public Task HandleClientUnsubscribedTopicAsync(string clientId, string topicFilter) { - ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(senderClientId, applicationMessage)); + var handler = ClientUnsubscribedTopicHandler; + if (handler == null) + { + return Task.FromResult(0); + } + + return handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs(clientId, topicFilter)); } - public void OnClientConnected(string clientId) + public Task HandleApplicationMessageReceivedAsync(string senderClientId, MqttApplicationMessage applicationMessage) { - ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientId)); + var handler = ApplicationMessageReceivedHandler; + if (handler == null) + { + return Task.FromResult(0); + } + + return handler.HandleApplicationMessageReceivedAsync(new MqttApplicationMessageReceivedEventArgs(senderClientId, applicationMessage)); } } } diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs index f43ff862..451940aa 100644 --- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs @@ -75,6 +75,12 @@ namespace MQTTnet.Server return this; } + public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPV6Address(IPAddress value) + { + _options.TlsEndpointOptions.BoundInterNetworkV6Address = value; + return this; + } + public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value) { _options.TlsEndpointOptions.Certificate = value; diff --git a/Source/MQTTnet/Server/MqttServerStartedHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerStartedHandlerDelegate.cs new file mode 100644 index 00000000..8a053574 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerStartedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerStartedHandlerDelegate : IMqttServerStartedHandler + { + private readonly Func _handler; + + public MqttServerStartedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public MqttServerStartedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleServerStartedAsync(EventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Source/MQTTnet/Server/MqttServerStoppedHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerStoppedHandlerDelegate.cs new file mode 100644 index 00000000..14e6a621 --- /dev/null +++ b/Source/MQTTnet/Server/MqttServerStoppedHandlerDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttServerStoppedHandlerDelegate : IMqttServerStoppedHandler + { + private readonly Func _handler; + + public MqttServerStoppedHandlerDelegate(Action handler) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + _handler = eventArgs => + { + handler(eventArgs); + return Task.FromResult(0); + }; + } + + public MqttServerStoppedHandlerDelegate(Func handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } + + public Task HandleServerStoppedAsync(EventArgs eventArgs) + { + return _handler(eventArgs); + } + } +} diff --git a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 1878c34c..c18a7313 100644 --- a/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using MQTTnet.Channel; -using MQTTnet.Client; using MQTTnet.Diagnostics; using MQTTnet.Implementations; using MQTTnet.Server; @@ -22,7 +21,7 @@ namespace MQTTnet.Benchmarks { var factory = new MqttFactory(); var tcpServer = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger()); - tcpServer.ClientAccepted += (sender, args) => _serverChannel = (IMqttChannel)args.Client.GetType().GetField("_channel", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(args.Client); + tcpServer.ClientAcceptedHandler += args => _serverChannel = (IMqttChannel)args.Client.GetType().GetField("_channel", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(args.Client); _mqttServer = factory.CreateMqttServer(new[] { tcpServer }, new MqttNetLogger()); diff --git a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs index d713dfca..35a001d0 100644 --- a/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -73,7 +73,9 @@ namespace MQTTnet.Benchmarks _position = _buffer.Offset; } - public string Endpoint { get; } + public string Endpoint { get; } = string.Empty; + + public bool IsSecureConnection { get; } = false; public void Reset() { diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj index 176cfcde..3d8d93ea 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj +++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs index 6c13dbbc..93fc2a60 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Client.Publishing; +using MQTTnet.Client.Receiving; using MQTTnet.Client.Subscribing; using MQTTnet.Client.Unsubscribing; using MQTTnet.Formatter; @@ -213,13 +214,13 @@ namespace MQTTnet.Tests.MQTTv5 var receivedMessages = new List(); await client1.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithClientId("client1").WithProtocolVersion(MqttProtocolVersion.V500).Build()); - client1.ApplicationMessageReceived += (s, e) => + client1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { lock (receivedMessages) { receivedMessages.Add(e); } - }; + }); await client1.SubscribeAsync("a"); diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs index 73676ddd..54441ef9 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttCommunicationAdapter.cs @@ -14,7 +14,9 @@ namespace MQTTnet.Tests.Mockups public TestMqttCommunicationAdapter Partner { get; set; } - public string Endpoint { get; } + public string Endpoint { get; } = string.Empty; + + public bool IsSecureConnection { get; } = false; public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311); diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs index 014a9063..b2970fb0 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestMqttServerAdapter.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Tests.Mockups { public class TestMqttServerAdapter : IMqttServerAdapter { - public event EventHandler ClientAccepted; + public Action ClientAcceptedHandler { get; set; } public async Task ConnectTestClient(string clientId, MqttApplicationMessage willMessage = null) { @@ -41,7 +41,7 @@ namespace MQTTnet.Tests.Mockups private void FireClientAcceptedEvent(IMqttChannelAdapter adapter) { - ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter)); + ClientAcceptedHandler?.Invoke(new MqttServerAdapterClientAcceptedEventArgs(adapter)); } public Task StartAsync(IMqttServerOptions options) diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs deleted file mode 100644 index 1cbd1b75..00000000 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; -using MQTTnet.Client; -using MQTTnet.Server; - -namespace MQTTnet.Tests.Mockups -{ - public static class TestServerExtensions - { - /// - /// publishes a message with a client and waits in the server until a message with the same topic is received - /// - /// - public static async Task PublishAndWaitForAsync(this IMqttClient client, IMqttServer server, MqttApplicationMessage message) - { - var tcs = new TaskCompletionSource(); - - void Handler(object sender, MqttApplicationMessageReceivedEventArgs args) - { - if (args.ApplicationMessage.Topic == message.Topic) - { - tcs.SetResult(true); - } - } - - server.ApplicationMessageReceived += Handler; - - try - { - await client.PublishAsync(message).ConfigureAwait(false); - await tcs.Task.ConfigureAwait(false); - } - finally - { - server.ApplicationMessageReceived -= Handler; - } - } - } -} diff --git a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs index d7af3293..6098adb7 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClientTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClientTests.cs @@ -5,8 +5,9 @@ using System.Net.Sockets; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; -using MQTTnet.Client.Receiving; using MQTTnet.Exceptions; using MQTTnet.Protocol; using MQTTnet.Server; @@ -45,10 +46,10 @@ namespace MQTTnet.Tests using (var client = factory.CreateMqttClient()) { Exception ex = null; - client.Disconnected += (s, e) => + client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(e => { ex = e.Exception; - }; + }); try { @@ -81,9 +82,9 @@ namespace MQTTnet.Tests var receivedValues = new List(); - async Task Handler1(MqttApplicationMessageHandlerContext context) + async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) { - var value = int.Parse(context.ApplicationMessage.ConvertPayloadToString()); + var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString()); await Task.Delay(value); lock (receivedValues) @@ -92,7 +93,7 @@ namespace MQTTnet.Tests } } - client1.UseReceivedApplicationMessageHandler(Handler1); + client1.UseApplicationMessageReceivedHandler(Handler1); var client2 = await testEnvironment.ConnectClientAsync(); for (var i = MessagesCount; i > 0; i--) @@ -119,27 +120,27 @@ namespace MQTTnet.Tests var client1 = await testEnvironment.ConnectClientAsync(); await client1.SubscribeAsync("request/+"); - async Task Handler1(MqttApplicationMessageHandlerContext context) + async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) { - await client1.PublishAsync($"reply/{context.ApplicationMessage.Topic}"); + await client1.PublishAsync($"reply/{eventArgs.ApplicationMessage.Topic}"); } - client1.UseReceivedApplicationMessageHandler(Handler1); + client1.UseApplicationMessageReceivedHandler(Handler1); var client2 = await testEnvironment.ConnectClientAsync(); await client2.SubscribeAsync("reply/#"); var replies = new List(); - void Handler2(MqttApplicationMessageHandlerContext context) + void Handler2(MqttApplicationMessageReceivedEventArgs eventArgs) { lock (replies) { - replies.Add(context.ApplicationMessage.Topic); + replies.Add(eventArgs.ApplicationMessage.Topic); } } - client2.UseReceivedApplicationMessageHandler((Action)Handler2); + client2.UseApplicationMessageReceivedHandler((Action)Handler2); await Task.Delay(500); @@ -163,7 +164,7 @@ namespace MQTTnet.Tests var receivedMessages = new List(); var client1 = await testEnvironment.ConnectClientAsync(); - client1.UseReceivedApplicationMessageHandler(c => + client1.UseApplicationMessageReceivedHandler(c => { lock (receivedMessages) { @@ -195,7 +196,7 @@ namespace MQTTnet.Tests var client = testEnvironment.CreateClient(); - client.Connected += async (s, e) => + client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => { await client.SubscribeAsync("RCU/P1/H0001/R0003"); @@ -204,9 +205,9 @@ namespace MQTTnet.Tests .WithTopic("RCU/P1/H0001/R0003"); await client.PublishAsync(msg.Build()); - }; + }); - client.UseReceivedApplicationMessageHandler(c => + client.UseApplicationMessageReceivedHandler(c => { lock (receivedMessages) { @@ -241,7 +242,7 @@ namespace MQTTnet.Tests var retries = 0; - async Task Handler1(MqttApplicationMessageHandlerContext context) + async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) { retries++; @@ -249,7 +250,7 @@ namespace MQTTnet.Tests throw new Exception("Broken!"); } - client1.UseReceivedApplicationMessageHandler(Handler1); + client1.UseApplicationMessageReceivedHandler(Handler1); var client2 = await testEnvironment.ConnectClientAsync(); await client2.PublishAsync("x"); diff --git a/Tests/MQTTnet.Core.Tests/MqttFactoryTests.cs b/Tests/MQTTnet.Core.Tests/MqttFactoryTests.cs index 3bdaa05d..b1a08df4 100644 --- a/Tests/MQTTnet.Core.Tests/MqttFactoryTests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttFactoryTests.cs @@ -59,7 +59,7 @@ namespace MQTTnet.Tests //wait at least connect timeout or we have some log messages var tcs = new TaskCompletionSource(); - managedClient.ConnectingFailed += (s, e) => tcs.TrySetResult(null); + managedClient.ConnectingFailedHandler = new ConnectingFailedHandlerDelegate(e => tcs.TrySetResult(null)); await Task.WhenAny(Task.Delay(managedClient.Options.ClientOptions.CommunicationTimeout), tcs.Task); } finally diff --git a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs new file mode 100644 index 00000000..b0babfa0 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs @@ -0,0 +1,55 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Tests.Mockups; +using MQTTnet.Client; +using MQTTnet.Client.Receiving; +using MQTTnet.Exceptions; +using MQTTnet.Extensions.Rpc; +using MQTTnet.Protocol; + +namespace MQTTnet.Tests +{ + [TestClass] + public class RPC_Tests + { + [TestMethod] + public async Task Execute_Success() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + var responseSender = await testEnvironment.ConnectClientAsync(); + await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping"); + + responseSender.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(async e => + { + await responseSender.PublishAsync(e.ApplicationMessage.Topic + "/response", "pong"); + }); + + var requestSender = await testEnvironment.ConnectClientAsync(); + + var rpcClient = new MqttRpcClient(requestSender); + var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); + + Assert.AreEqual("pong", Encoding.UTF8.GetString(response)); + } + } + + [TestMethod] + [ExpectedException(typeof(MqttCommunicationTimedOutException))] + public async Task Execute_Timeout() + { + using (var testEnvironment = new TestEnvironment()) + { + await testEnvironment.StartServerAsync(); + + var requestSender = await testEnvironment.ConnectClientAsync(); + + var rpcClient = new MqttRpcClient(requestSender); + await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); + } + } + } +} diff --git a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs index c8de9e40..4cd29cef 100644 --- a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs +++ b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Server; namespace MQTTnet.Tests @@ -29,10 +30,10 @@ namespace MQTTnet.Tests TaskCompletionSource response = null; - receiverClient.ApplicationMessageReceived += (sender, args) => + receiverClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(args => { response?.SetResult(args.ApplicationMessage.ConvertPayloadToString()); - }; + }); var times = new List(); var stopwatch = Stopwatch.StartNew(); diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index b9efc7ac..5dbde0e6 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -9,7 +9,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; using MQTTnet.Client; using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Protocol; using MQTTnet.Server; using MQTTnet.Tests.Mockups; @@ -66,7 +68,7 @@ namespace MQTTnet.Tests var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); var c1 = await testEnvironment.ConnectClientAsync(); - c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + c1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); var c2 = await testEnvironment.ConnectClientAsync(clientOptions); @@ -92,7 +94,7 @@ namespace MQTTnet.Tests var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); var c1 = await testEnvironment.ConnectClientAsync(); - c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); var c2 = await testEnvironment.ConnectClientAsync(clientOptions); @@ -114,7 +116,7 @@ namespace MQTTnet.Tests var server = await testEnvironment.StartServerAsync(); var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("c1")); - c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("c2")); @@ -125,10 +127,10 @@ namespace MQTTnet.Tests Assert.AreEqual(0, receivedMessagesCount); var subscribeEventCalled = false; - server.ClientSubscribedTopic += (_, e) => + server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedHandlerDelegate(e => { subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1"; - }; + }); await c1.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); await Task.Delay(250); @@ -139,10 +141,10 @@ namespace MQTTnet.Tests Assert.AreEqual(1, receivedMessagesCount); var unsubscribeEventCalled = false; - server.ClientUnsubscribedTopic += (_, e) => + server.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(e => { unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == "c1"; - }; + }); await c1.UnsubscribeAsync("a"); await Task.Delay(250); @@ -168,7 +170,7 @@ namespace MQTTnet.Tests var receivedMessagesCount = 0; var client = await testEnvironment.ConnectClientAsync(); - client.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + client.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); await client.SubscribeAsync(new TopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); @@ -194,7 +196,7 @@ namespace MQTTnet.Tests var c1 = await testEnvironment.ConnectClientAsync(); var c2 = await testEnvironment.ConnectClientAsync(); - c1.UseReceivedApplicationMessageHandler(c => + c1.UseApplicationMessageReceivedHandler(c => { lock (locked) { @@ -202,7 +204,7 @@ namespace MQTTnet.Tests } }); - c2.UseReceivedApplicationMessageHandler(c => + c2.UseApplicationMessageReceivedHandler(c => { lock (locked) { @@ -258,12 +260,12 @@ namespace MQTTnet.Tests var client = await testEnvironment.ConnectClientAsync(); var receivedMessages = new List(); - client.Connected += async (s, e) => + client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => { await client.PublishAsync("Connected"); - }; + }); - client.UseReceivedApplicationMessageHandler(c => + client.UseApplicationMessageReceivedHandler(c => { lock (receivedMessages) { @@ -287,15 +289,12 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment()) { var server = await testEnvironment.StartServerAsync(); - server.ClientConnected += async (s, e) => - { - await server.SubscribeAsync(e.ClientId, "topic1"); - }; + server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e => server.SubscribeAsync(e.ClientId, "topic1")); var client = await testEnvironment.ConnectClientAsync(); var receivedMessages = new List(); - client.UseReceivedApplicationMessageHandler(c => + client.UseApplicationMessageReceivedHandler(c => { lock (receivedMessages) { @@ -325,7 +324,7 @@ namespace MQTTnet.Tests var disconnectCalled = 0; var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); - c1.Disconnected += (sender, args) => disconnectCalled++; + c1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(e => disconnectCalled++); await Task.Delay(100); @@ -347,8 +346,8 @@ namespace MQTTnet.Tests var clientConnectedCalled = 0; var clientDisconnectedCalled = 0; - server.ClientConnected += (_, __) => Interlocked.Increment(ref clientConnectedCalled); - server.ClientDisconnected += (_, __) => Interlocked.Increment(ref clientDisconnectedCalled); + server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(_ => Interlocked.Increment(ref clientConnectedCalled)); + server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(_ => Interlocked.Increment(ref clientDisconnectedCalled)); var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); @@ -462,7 +461,7 @@ namespace MQTTnet.Tests var receivedMessages = 0; var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseReceivedApplicationMessageHandler(c => + c2.UseApplicationMessageReceivedHandler(c => { Interlocked.Increment(ref receivedMessages); }); @@ -500,7 +499,7 @@ namespace MQTTnet.Tests var receivedMessagesCount = 0; var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + c2.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained_other").Build()); await Task.Delay(500); @@ -523,7 +522,7 @@ namespace MQTTnet.Tests var receivedMessages = new List(); var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseReceivedApplicationMessageHandler(c => + c2.UseApplicationMessageReceivedHandler(c => { lock (receivedMessages) { @@ -558,7 +557,7 @@ namespace MQTTnet.Tests var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + c2.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await Task.Delay(200); await c2.SubscribeAsync(new TopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); @@ -604,7 +603,7 @@ namespace MQTTnet.Tests await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build()); var isIntercepted = false; - c2.UseReceivedApplicationMessageHandler(c => + c2.UseApplicationMessageReceivedHandler(c => { isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(c.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; }); @@ -645,7 +644,7 @@ namespace MQTTnet.Tests await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); var client1 = await testEnvironment.ConnectClientAsync(); - client1.UseReceivedApplicationMessageHandler(c => + client1.UseApplicationMessageReceivedHandler(c => { receivedBody = c.ApplicationMessage.Payload; }); @@ -703,21 +702,21 @@ namespace MQTTnet.Tests var events = new List(); - server.ClientConnected += (_, __) => + server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(_ => { lock (events) { events.Add("c"); } - }; + }); - server.ClientDisconnected += (_, __) => + server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(_ => { lock (events) { events.Add("d"); } - }; + }); var clientOptions = new MqttClientOptionsBuilder() .WithClientId("same_id"); @@ -888,7 +887,7 @@ namespace MQTTnet.Tests var buffer = new StringBuilder(); - client2.UseReceivedApplicationMessageHandler(c => + client2.UseApplicationMessageReceivedHandler(c => { lock (buffer) { @@ -952,7 +951,7 @@ namespace MQTTnet.Tests await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("receiver")); - c1.UseReceivedApplicationMessageHandler(c => Interlocked.Increment(ref receivedMessagesCount)); + c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("sender")); diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs index db352f3c..3a58acf7 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; -using MQTTnet; using MQTTnet.AspNetCore; using MQTTnet.Server; @@ -36,7 +35,7 @@ namespace MQTTnet.TestApp.AspNetCore2 //app.UseMqttEndpoint(); app.UseMqttServer(server => { - server.Started += async (sender, args) => + server.StartedHandler = new MqttServerStartedHandlerDelegate(async args => { var msg = new MqttApplicationMessageBuilder() .WithPayload("Mqtt is awesome") @@ -58,7 +57,7 @@ namespace MQTTnet.TestApp.AspNetCore2 await Task.Delay(TimeSpan.FromSeconds(2)); } } - }; + }); }); app.Use((context, next) => diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/package.json b/Tests/MQTTnet.TestApp.AspNetCore2/package.json index 56c9018c..9363db99 100644 --- a/Tests/MQTTnet.TestApp.AspNetCore2/package.json +++ b/Tests/MQTTnet.TestApp.AspNetCore2/package.json @@ -2,13 +2,11 @@ "version": "1.0.0", "name": "mqtt.test", "private": true, - "devDependencies": { - }, - + "devDependencies": {}, "dependencies": { "mqtt": "2.15.1", "@types/node": "8.0.46", "systemjs": "0.20.19", "typescript": "2.3.4" } -} \ No newline at end of file +} diff --git a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs index 8343d9e7..09f85777 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs @@ -2,7 +2,10 @@ using System.Text; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Protocol; namespace MQTTnet.TestApp.NetCore @@ -25,7 +28,7 @@ namespace MQTTnet.TestApp.NetCore } }; - client.ApplicationMessageReceived += (s, e) => + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); @@ -33,18 +36,18 @@ namespace MQTTnet.TestApp.NetCore Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine(); - }; + }); - client.Connected += async (s, e) => + client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => { Console.WriteLine("### CONNECTED WITH SERVER ###"); await client.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); Console.WriteLine("### SUBSCRIBED ###"); - }; + }); - client.Disconnected += async (s, e) => + client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(async e => { Console.WriteLine("### DISCONNECTED FROM SERVER ###"); await Task.Delay(TimeSpan.FromSeconds(5)); @@ -57,7 +60,7 @@ namespace MQTTnet.TestApp.NetCore { Console.WriteLine("### RECONNECTING FAILED ###"); } - }; + }); try { diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index d7cea499..0d4f8f9d 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -4,6 +4,7 @@ using System.IO; using Newtonsoft.Json; using System.Collections.Generic; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Extensions.ManagedClient; using MQTTnet.Protocol; @@ -34,10 +35,10 @@ namespace MQTTnet.TestApp.NetCore try { var managedClient = new MqttFactory().CreateManagedMqttClient(); - managedClient.ApplicationMessageReceived += (s, e) => + managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); - }; + }); await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("1")); await managedClient.PublishAsync(builder => builder.WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS()); diff --git a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs index ac6d16c5..7f322130 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Formatter; using MQTTnet.Protocol; using Newtonsoft.Json; @@ -80,7 +81,7 @@ namespace MQTTnet.TestApp.NetCore var topic = Guid.NewGuid().ToString(); MqttApplicationMessage receivedMessage = null; - client.ApplicationMessageReceived += (s, e) => receivedMessage = e.ApplicationMessage; + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => receivedMessage = e.ApplicationMessage); await client.ConnectAsync(options); await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce); diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs index b00e3ac7..bd626719 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -1,6 +1,7 @@ using System; using System.Text; using System.Threading.Tasks; +using MQTTnet.Client.Receiving; using MQTTnet.Protocol; using MQTTnet.Server; @@ -78,12 +79,12 @@ namespace MQTTnet.TestApp.NetCore var mqttServer = new MqttFactory().CreateMqttServer(); - mqttServer.ApplicationMessageReceived += (s, e) => + mqttServer.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { MqttNetConsoleLogger.PrintToConsole( $"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", ConsoleColor.Magenta); - }; + }); //options.ApplicationMessageInterceptor = c => //{ @@ -107,10 +108,10 @@ namespace MQTTnet.TestApp.NetCore // } //}; - mqttServer.ClientDisconnected += (s, e) => + mqttServer.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e => { Console.Write("Client disconnected event fired."); - }; + }); await mqttServer.StartAsync(options); diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index eaf20872..d79fe06d 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -7,7 +7,10 @@ using Windows.Security.Cryptography.Certificates; using Windows.UI.Core; using Windows.UI.Xaml; using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; +using MQTTnet.Client.Receiving; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Extensions.ManagedClient; @@ -144,9 +147,9 @@ namespace MQTTnet.TestApp.UniversalWindows if (_mqttClient != null) { await _mqttClient.DisconnectAsync(); - _mqttClient.ApplicationMessageReceived -= OnApplicationMessageReceived; - _mqttClient.Connected -= OnConnected; - _mqttClient.Disconnected -= OnDisconnected; + _mqttClient.UseApplicationMessageReceivedHandler(HandleReceivedApplicationMessage); + _mqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(x => OnConnected(x)); + _mqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(x => OnDisconnected(x)); } var factory = new MqttFactory(); @@ -154,9 +157,9 @@ namespace MQTTnet.TestApp.UniversalWindows if (UseManagedClient.IsChecked == true) { _managedMqttClient = factory.CreateManagedMqttClient(); - _managedMqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; - _managedMqttClient.Connected += OnConnected; - _managedMqttClient.Disconnected += OnDisconnected; + _managedMqttClient.UseApplicationMessageReceivedHandler(HandleReceivedApplicationMessage); + _managedMqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(x => OnConnected(x)); + _managedMqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(x => OnDisconnected(x)); await _managedMqttClient.StartAsync(new ManagedMqttClientOptions { @@ -166,9 +169,9 @@ namespace MQTTnet.TestApp.UniversalWindows else { _mqttClient = factory.CreateMqttClient(); - _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; - _mqttClient.Connected += OnConnected; - _mqttClient.Disconnected += OnDisconnected; + _mqttClient.UseApplicationMessageReceivedHandler(HandleReceivedApplicationMessage); + _mqttClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(x => OnConnected(x)); + _mqttClient.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(x => OnDisconnected(x)); await _mqttClient.ConnectAsync(options); } @@ -179,7 +182,7 @@ namespace MQTTnet.TestApp.UniversalWindows } } - private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs e) + private void OnDisconnected(MqttClientDisconnectedEventArgs e) { _traceMessages.Enqueue(new MqttNetLogMessage("", DateTime.Now, -1, "", MqttNetLogLevel.Info, "! DISCONNECTED EVENT FIRED", null)); @@ -187,7 +190,7 @@ namespace MQTTnet.TestApp.UniversalWindows Task.Run(UpdateLogAsync); } - private void OnConnected(object sender, MqttClientConnectedEventArgs e) + private void OnConnected(MqttClientConnectedEventArgs e) { _traceMessages.Enqueue(new MqttNetLogMessage("", DateTime.Now, -1, "", MqttNetLogLevel.Info, "! CONNECTED EVENT FIRED", null)); @@ -195,7 +198,7 @@ namespace MQTTnet.TestApp.UniversalWindows Task.Run(UpdateLogAsync); } - private async void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) + private async Task HandleReceivedApplicationMessage(MqttApplicationMessageReceivedEventArgs eventArgs) { var item = $"Timestamp: {DateTime.Now:O} | Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {Encoding.UTF8.GetString(eventArgs.ApplicationMessage.Payload)} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel}";