From b68247f56d99d1d82ccc917ee88dd92c4d47c754 Mon Sep 17 00:00:00 2001 From: Kiarash Ghiaseddin Date: Mon, 4 Mar 2019 21:13:22 +0100 Subject: [PATCH 01/17] WIP First draft for MQTTnetServer project * Basic support for settings * Nasty code for setting up kestrel from appsettings.json --- .gitignore | 1 + MQTTnet.sln | 19 +++ .../Controllers/ValuesController.cs | 45 +++++++ Source/MQTTnetServer/MQTTnetServer.csproj | 41 ++++++ Source/MQTTnetServer/MQTTnetServer.xml | 116 +++++++++++++++++ Source/MQTTnetServer/Program.cs | 118 ++++++++++++++++++ Source/MQTTnetServer/Settings/ListenModel.cs | 30 +++++ .../Settings/ListenProtocolTypes.cs | 23 ++++ .../MQTTnetServer/Settings/SettingsModel.cs | 15 +++ Source/MQTTnetServer/Startup.cs | 61 +++++++++ .../appsettings.Development.json | 9 ++ Source/MQTTnetServer/appsettings.json | 32 +++++ .../MQTTnet.TestApp.AspNetCore2/package.json | 6 +- 13 files changed, 512 insertions(+), 4 deletions(-) create mode 100644 Source/MQTTnetServer/Controllers/ValuesController.cs create mode 100644 Source/MQTTnetServer/MQTTnetServer.csproj create mode 100644 Source/MQTTnetServer/MQTTnetServer.xml create mode 100644 Source/MQTTnetServer/Program.cs create mode 100644 Source/MQTTnetServer/Settings/ListenModel.cs create mode 100644 Source/MQTTnetServer/Settings/ListenProtocolTypes.cs create mode 100644 Source/MQTTnetServer/Settings/SettingsModel.cs create mode 100644 Source/MQTTnetServer/Startup.cs create mode 100644 Source/MQTTnetServer/appsettings.Development.json create mode 100644 Source/MQTTnetServer/appsettings.json 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/MQTTnet.sln b/MQTTnet.sln index 309b11fe..033fe998 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}") = "MQTTnetServer", "Source\MQTTnetServer\MQTTnetServer.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/Source/MQTTnetServer/Controllers/ValuesController.cs b/Source/MQTTnetServer/Controllers/ValuesController.cs new file mode 100644 index 00000000..173fc340 --- /dev/null +++ b/Source/MQTTnetServer/Controllers/ValuesController.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace MQTTnetServer.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/MQTTnetServer/MQTTnetServer.csproj b/Source/MQTTnetServer/MQTTnetServer.csproj new file mode 100644 index 00000000..2dbf6084 --- /dev/null +++ b/Source/MQTTnetServer/MQTTnetServer.csproj @@ -0,0 +1,41 @@ + + + + netcoreapp2.2 + InProcess + MQTTnetServer + MQTTnetServer + False + + + + + + false + false + latest + + + + MQTTnetServer.xml + NU1605 + 1701;1702 + + + + + Always + + + + + + + + + + + + + + diff --git a/Source/MQTTnetServer/MQTTnetServer.xml b/Source/MQTTnetServer/MQTTnetServer.xml new file mode 100644 index 00000000..762bc400 --- /dev/null +++ b/Source/MQTTnetServer/MQTTnetServer.xml @@ -0,0 +1,116 @@ + + + + MQTTnetServer + + + + + Main Entry point + + + + + Main + + + + + + Configure and Start Kestrel + + + + + + + Read Application Settings + + + + + + Listen Entry Settings Model + + + + + Constructor + + + + + Listen Address + + + + + Listen Port + + + + + Protocol Type + + + + + Listen Protocol Types + + + + + HTTP + + + + + HTTPS + + + + + MQTT + + + + + Main Settings Model + + + + + Listen Settings + + + + + Web App Startup + + + + + Constructor + + + + + + Application Settings + + + + + This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + + + + + + + This method gets called by the runtime. Use this method to add services to the container. + + + + + diff --git a/Source/MQTTnetServer/Program.cs b/Source/MQTTnetServer/Program.cs new file mode 100644 index 00000000..cdcb35d9 --- /dev/null +++ b/Source/MQTTnetServer/Program.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using MQTTnet.AspNetCore; +using MQTTnetServer.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; + +namespace MQTTnetServer +{ + /// + /// Main Entry point + /// + public class Program + { + /// + /// Main + /// + /// + public static void Main(string[] args) + { + try + { + CreateWebHostBuilder(args).Build().Run(); + } + catch (FileNotFoundException e) + { + Console.WriteLine("Could not find application settings file in: " + e.FileName); + return; + } + } + + /// + /// Configure and Start Kestrel + /// + /// + /// + public static IWebHostBuilder CreateWebHostBuilder(string[] args) + { + var webHost = WebHost.CreateDefaultBuilder(args); + var listen = ReadListenSettings(); + webHost + .UseKestrel(o => + { + if (listen?.Length > 0) + { + foreach (var item in listen) + { + if (item.Address?.Trim() == "*") + { + if (item.Protocol == ListenProtocolTypes.MQTT) + { + o.ListenAnyIP(item.Port, c => c.UseMqtt()); + } + else + { + o.ListenAnyIP(item.Port); + } + } + else if (item.Address?.Trim() == "localhost") + { + if (item.Protocol == ListenProtocolTypes.MQTT) + { + o.ListenLocalhost(item.Port, c => c.UseMqtt()); + } + else + { + o.ListenLocalhost(item.Port); + } + } + else + { + if (IPAddress.TryParse(item.Address, out var ip)) + { + if (item.Protocol == ListenProtocolTypes.MQTT) + { + o.Listen(ip, item.Port, c => c.UseMqtt()); + } + else + { + o.Listen(ip, item.Port); + } + } + } + } + } + else + { + o.ListenAnyIP(1883, l => l.UseMqtt()); + o.ListenAnyIP(5000); + } + }); + + webHost.UseStartup(); + + return webHost; + } + + /// + /// Read Application Settings + /// + /// + public static ListenModel[] ReadListenSettings() + { + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); + + var listen = new List(); + builder.Bind("MQTTnetServer:Listen", listen); + + return listen.ToArray(); + } + } +} diff --git a/Source/MQTTnetServer/Settings/ListenModel.cs b/Source/MQTTnetServer/Settings/ListenModel.cs new file mode 100644 index 00000000..5af984f6 --- /dev/null +++ b/Source/MQTTnetServer/Settings/ListenModel.cs @@ -0,0 +1,30 @@ +namespace MQTTnetServer.Settings +{ + /// + /// Listen Entry Settings Model + /// + public class ListenModel + { + /// + /// Constructor + /// + public ListenModel() + { + } + + /// + /// Listen Address + /// + public string Address { get; set; } + + /// + /// Listen Port + /// + public int Port { get; set; } + + /// + /// Protocol Type + /// + public ListenProtocolTypes Protocol { get; set; } = ListenProtocolTypes.HTTP; + } +} \ No newline at end of file diff --git a/Source/MQTTnetServer/Settings/ListenProtocolTypes.cs b/Source/MQTTnetServer/Settings/ListenProtocolTypes.cs new file mode 100644 index 00000000..5ca4d744 --- /dev/null +++ b/Source/MQTTnetServer/Settings/ListenProtocolTypes.cs @@ -0,0 +1,23 @@ +namespace MQTTnetServer.Settings +{ + /// + /// Listen Protocol Types + /// + public enum ListenProtocolTypes + { + /// + /// HTTP + /// + HTTP = 0, + + /// + /// HTTPS + /// + HTTPS = 1, + + /// + /// MQTT + /// + MQTT = 20 + } +} \ No newline at end of file diff --git a/Source/MQTTnetServer/Settings/SettingsModel.cs b/Source/MQTTnetServer/Settings/SettingsModel.cs new file mode 100644 index 00000000..267ace11 --- /dev/null +++ b/Source/MQTTnetServer/Settings/SettingsModel.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MQTTnetServer.Settings +{ + /// + /// Main Settings Model + /// + public class SettingsModel + { + /// + /// Listen Settings + /// + public IEnumerable Listen { get; set; } + } +} \ No newline at end of file diff --git a/Source/MQTTnetServer/Startup.cs b/Source/MQTTnetServer/Startup.cs new file mode 100644 index 00000000..4c027568 --- /dev/null +++ b/Source/MQTTnetServer/Startup.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace MQTTnetServer +{ + /// + /// Web App Startup + /// + public class Startup + { + /// + /// Constructor + /// + /// + public Startup(IConfiguration configuration) + { + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + /// + /// Application Settings + /// + public IConfigurationRoot Configuration { get; } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// + /// + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnetServer/appsettings.Development.json b/Source/MQTTnetServer/appsettings.Development.json new file mode 100644 index 00000000..e203e940 --- /dev/null +++ b/Source/MQTTnetServer/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Source/MQTTnetServer/appsettings.json b/Source/MQTTnetServer/appsettings.json new file mode 100644 index 00000000..0bf2d9e3 --- /dev/null +++ b/Source/MQTTnetServer/appsettings.json @@ -0,0 +1,32 @@ +{ + "MQTTnetServer": { + /* + Wildcard Addresses: + * - All local IP addresses + localhost - Localhost only + + Supported Protocols: + 0 - HTTP + 1 - HTTPS + 20 - MQTT + */ + "Listen": [ + { + "Address": "localhost", + "Port": 5000, + "Protocol": 0 + }, + { + "Address": "*", + "Port": 1883, + "Protocol": 20 + } + ] + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} 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 +} From 6f5f507323ca109a16126a33682adaf3d60a06bd Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 5 Mar 2019 21:37:00 +0100 Subject: [PATCH 02/17] Extend MQTTnet.Server application. --- MQTTnet.sln | 2 +- MQTTnet.sln.DotSettings | 1 + .../MqttWebSocketServerAdapter.cs | 1 - .../Configuration/EndpointConfiguration.cs | 9 ++ .../Configuration}/ListenModel.cs | 2 +- .../Configuration}/ListenProtocolTypes.cs | 2 +- .../MqttNetServerConfiguration.cs | 7 + .../Configuration}/SettingsModel.cs | 2 +- .../Controllers/ValuesController.cs | 7 +- Source/MQTTnet.Server/LICENSE | 21 +++ .../Logging/MqttNetChildLoggerWrapper.cs | 43 ++++++ .../Logging/MqttNetLoggerWrapper.cs | 48 ++++++ .../MQTTnet.Server.csproj} | 28 +++- .../Mqtt/MqttApplicationMessageInterceptor.cs | 50 ++++++ .../Mqtt/MqttConnectionValidator.cs | 46 ++++++ .../MQTTnet.Server/Mqtt/MqttServerService.cs | 101 +++++++++++++ .../Mqtt/MqttSubscriptionInterceptor.cs | 47 ++++++ Source/MQTTnet.Server/Program.cs | 143 ++++++++++++++++++ .../DataSharing/DataSharingService.cs | 47 ++++++ .../MQTTnet.Server/Scripting/PythonConvert.cs | 88 +++++++++++ .../Scripting/PythonIOStream.cs | 66 ++++++++ .../Scripting/PythonScriptHostService.cs | 130 ++++++++++++++++ .../Scripting/PythonScriptInstance.cs | 57 +++++++ Source/MQTTnet.Server/Scripts/00_sample.py | 74 +++++++++ Source/MQTTnet.Server/Scripts/readme.md | 19 +++ Source/MQTTnet.Server/Startup.cs | 69 +++++++++ .../appsettings.Development.json | 0 .../appsettings.json | 0 Source/MQTTnet/MqttFactory.cs | 18 +++ Source/MQTTnetServer/MQTTnetServer.xml | 116 -------------- Source/MQTTnetServer/Program.cs | 118 --------------- Source/MQTTnetServer/Startup.cs | 61 -------- 32 files changed, 1114 insertions(+), 309 deletions(-) create mode 100644 Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs rename Source/{MQTTnetServer/Settings => MQTTnet.Server/Configuration}/ListenModel.cs (93%) rename Source/{MQTTnetServer/Settings => MQTTnet.Server/Configuration}/ListenProtocolTypes.cs (89%) create mode 100644 Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs rename Source/{MQTTnetServer/Settings => MQTTnet.Server/Configuration}/SettingsModel.cs (87%) rename Source/{MQTTnetServer => MQTTnet.Server}/Controllers/ValuesController.cs (86%) create mode 100644 Source/MQTTnet.Server/LICENSE create mode 100644 Source/MQTTnet.Server/Logging/MqttNetChildLoggerWrapper.cs create mode 100644 Source/MQTTnet.Server/Logging/MqttNetLoggerWrapper.cs rename Source/{MQTTnetServer/MQTTnetServer.csproj => MQTTnet.Server/MQTTnet.Server.csproj} (55%) create mode 100644 Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttServerService.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs create mode 100644 Source/MQTTnet.Server/Program.cs create mode 100644 Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs create mode 100644 Source/MQTTnet.Server/Scripting/PythonConvert.cs create mode 100644 Source/MQTTnet.Server/Scripting/PythonIOStream.cs create mode 100644 Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs create mode 100644 Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs create mode 100644 Source/MQTTnet.Server/Scripts/00_sample.py create mode 100644 Source/MQTTnet.Server/Scripts/readme.md create mode 100644 Source/MQTTnet.Server/Startup.cs rename Source/{MQTTnetServer => MQTTnet.Server}/appsettings.Development.json (100%) rename Source/{MQTTnetServer => MQTTnet.Server}/appsettings.json (100%) delete mode 100644 Source/MQTTnetServer/MQTTnetServer.xml delete mode 100644 Source/MQTTnetServer/Program.cs delete mode 100644 Source/MQTTnetServer/Startup.cs diff --git a/MQTTnet.sln b/MQTTnet.sln index 033fe998..f2fff839 100644 --- a/MQTTnet.sln +++ b/MQTTnet.sln @@ -47,7 +47,7 @@ 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}") = "MQTTnetServer", "Source\MQTTnetServer\MQTTnetServer.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 65514d0b..41039b53 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -40,7 +40,6 @@ namespace MQTTnet.AspNetCore public void Dispose() { - StopAsync().GetAwaiter().GetResult(); } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs b/Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs new file mode 100644 index 00000000..b4b04961 --- /dev/null +++ b/Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Server.Configuration +{ + public class EndpointConfiguration + { + public int Port { get; set; } = 1883; + + public int BacklogSize { get; set; } = 10; + } +} diff --git a/Source/MQTTnetServer/Settings/ListenModel.cs b/Source/MQTTnet.Server/Configuration/ListenModel.cs similarity index 93% rename from Source/MQTTnetServer/Settings/ListenModel.cs rename to Source/MQTTnet.Server/Configuration/ListenModel.cs index 5af984f6..bf62a687 100644 --- a/Source/MQTTnetServer/Settings/ListenModel.cs +++ b/Source/MQTTnet.Server/Configuration/ListenModel.cs @@ -1,4 +1,4 @@ -namespace MQTTnetServer.Settings +namespace MQTTnet.Server.Configuration { /// /// Listen Entry Settings Model diff --git a/Source/MQTTnetServer/Settings/ListenProtocolTypes.cs b/Source/MQTTnet.Server/Configuration/ListenProtocolTypes.cs similarity index 89% rename from Source/MQTTnetServer/Settings/ListenProtocolTypes.cs rename to Source/MQTTnet.Server/Configuration/ListenProtocolTypes.cs index 5ca4d744..84a0a48b 100644 --- a/Source/MQTTnetServer/Settings/ListenProtocolTypes.cs +++ b/Source/MQTTnet.Server/Configuration/ListenProtocolTypes.cs @@ -1,4 +1,4 @@ -namespace MQTTnetServer.Settings +namespace MQTTnet.Server.Configuration { /// /// Listen Protocol Types diff --git a/Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs b/Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs new file mode 100644 index 00000000..632b8d10 --- /dev/null +++ b/Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs @@ -0,0 +1,7 @@ +namespace MQTTnet.Server.Configuration +{ + public class MqttNetServerConfiguration + { + + } +} diff --git a/Source/MQTTnetServer/Settings/SettingsModel.cs b/Source/MQTTnet.Server/Configuration/SettingsModel.cs similarity index 87% rename from Source/MQTTnetServer/Settings/SettingsModel.cs rename to Source/MQTTnet.Server/Configuration/SettingsModel.cs index 267ace11..e3930848 100644 --- a/Source/MQTTnetServer/Settings/SettingsModel.cs +++ b/Source/MQTTnet.Server/Configuration/SettingsModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace MQTTnetServer.Settings +namespace MQTTnet.Server.Configuration { /// /// Main Settings Model diff --git a/Source/MQTTnetServer/Controllers/ValuesController.cs b/Source/MQTTnet.Server/Controllers/ValuesController.cs similarity index 86% rename from Source/MQTTnetServer/Controllers/ValuesController.cs rename to Source/MQTTnet.Server/Controllers/ValuesController.cs index 173fc340..bfcc398f 100644 --- a/Source/MQTTnetServer/Controllers/ValuesController.cs +++ b/Source/MQTTnet.Server/Controllers/ValuesController.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; -namespace MQTTnetServer.Controllers +namespace MQTTnet.Server.Controllers { [Route("api/[controller]")] [ApiController] 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/MQTTnetServer/MQTTnetServer.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj similarity index 55% rename from Source/MQTTnetServer/MQTTnetServer.csproj rename to Source/MQTTnet.Server/MQTTnet.Server.csproj index 2dbf6084..f142e237 100644 --- a/Source/MQTTnetServer/MQTTnetServer.csproj +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -3,11 +3,11 @@ netcoreapp2.2 InProcess - MQTTnetServer - MQTTnetServer + MQTTnet.Server + MQTTnet.Server False - + MQTTnet @@ -16,8 +16,12 @@ latest + + + + - MQTTnetServer.xml + NU1605 1701;1702 @@ -29,8 +33,12 @@ + + + + @@ -38,4 +46,16 @@ + + + Always + + + Always + + + Always + + + diff --git a/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs new file mode 100644 index 00000000..4af2a557 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs @@ -0,0 +1,50 @@ +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; + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs new file mode 100644 index 00000000..0471b47e --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs @@ -0,0 +1,46 @@ +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 }, + { "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..6588ab50 --- /dev/null +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -0,0 +1,101 @@ +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.Logging; +using MQTTnet.Server.Scripting; + +namespace MQTTnet.Server.Mqtt +{ + public class MqttServerService + { + private readonly ILogger _logger; + + private readonly MqttConnectionValidator _mqttConnectionValidator; + private readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor; + private readonly MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor; + private readonly PythonScriptHostService _pythonScriptHostService; + + private readonly IMqttServer _mqttServer; + + public MqttServerService( + IMqttServerFactory mqttServerFactory, + MqttWebSocketServerAdapter webSocketServerAdapter, + MqttNetLoggerWrapper mqttNetLogger, + MqttConnectionValidator mqttConnectionValidator, + MqttSubscriptionInterceptor mqttSubscriptionInterceptor, + MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, + PythonScriptHostService pythonScriptHostService, + ILogger logger) + { + _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)); + + var adapters = new List + { + new MqttTcpServerAdapter(new MqttNetChildLoggerWrapper(null, mqttNetLogger)), + webSocketServerAdapter + }; + + _mqttServer = mqttServerFactory.CreateMqttServer(adapters); + } + + public void Configure() + { + _pythonScriptHostService.RegisterProxyObject("publish", new Action(Publish)); + + var options = new MqttServerOptionsBuilder() + .WithDefaultEndpoint() + .WithDefaultEndpointPort(1883) + .WithConnectionValidator(_mqttConnectionValidator) + .WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) + .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) + .Build(); + + _mqttServer.StartAsync(options).GetAwaiter().GetResult(); + + _logger.LogInformation("MQTT server started."); + } + + private void Publish(PythonDictionary parameters) + { + 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); + var binaryPayload = new byte[0]; + + 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(); + } + + applicationMessageBuilder = applicationMessageBuilder + .WithPayload(binaryPayload); + + var applicationMessage = applicationMessageBuilder.Build(); + + _mqttServer.PublishAsync(applicationMessage).GetAwaiter().GetResult(); + _logger.LogInformation($"Published topic '{applicationMessage.Topic}' from server."); + } + } +} diff --git a/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs new file mode 100644 index 00000000..115e0ab8 --- /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..a9b3be67 --- /dev/null +++ b/Source/MQTTnet.Server/Program.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using MQTTnet.Server.Configuration; + +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 void PrintLogo() + { + 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) +"); + + 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(); + } + + private static IWebHostBuilder CreateWebHostBuilder(string[] args) + { + var webHost = WebHost.CreateDefaultBuilder(args) + .UseKestrel(kestrelOptions => + { + kestrelOptions.ListenAnyIP(80, listenOptions => + { + listenOptions.NoDelay = true; + }); + }); + + //var listen = ReadListenSettings(); + //webHost + // .UseKestrel(o => + // { + // if (listen?.Length > 0) + // { + // foreach (var item in listen) + // { + // if (item.Address?.Trim() == "*") + // { + // if (item.Protocol == ListenProtocolTypes.MQTT) + // { + // o.ListenAnyIP(item.Port, c => c.UseMqtt()); + // } + // else + // { + // o.ListenAnyIP(item.Port); + // } + // } + // else if (item.Address?.Trim() == "localhost") + // { + // if (item.Protocol == ListenProtocolTypes.MQTT) + // { + // o.ListenLocalhost(item.Port, c => c.UseMqtt()); + // } + // else + // { + // o.ListenLocalhost(item.Port); + // } + // } + // else + // { + // if (IPAddress.TryParse(item.Address, out var ip)) + // { + // if (item.Protocol == ListenProtocolTypes.MQTT) + // { + // o.Listen(ip, item.Port, c => c.UseMqtt()); + // } + // else + // { + // o.Listen(ip, item.Port); + // } + // } + // } + // } + // } + // else + // { + // o.ListenAnyIP(1883, l => l.UseMqtt()); + // o.ListenAnyIP(5000); + // } + // }); + + webHost.UseStartup(); + + return webHost; + } + + public static ListenModel[] ReadListenSettings() + { + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .Build(); + + var listen = new List(); + builder.Bind("MQTTnetServer:Listen", listen); + + return listen.ToArray(); + } + } +} 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..4e8aff9b --- /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 action) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (action == null) throw new ArgumentNullException(nameof(action)); + + _proxyObjects.Add(name, action); + } + + 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..51763d89 --- /dev/null +++ b/Source/MQTTnet.Server/Scripts/00_sample.py @@ -0,0 +1,74 @@ +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"])) 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..ad7f40ea --- /dev/null +++ b/Source/MQTTnet.Server/Startup.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using MQTTnet.AspNetCore; +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.UseHttpsRedirection(); + application.UseMvc(); + + dataSharingService.Configure(); + pythonScriptHostService.Configure(); + mqttServerService.Configure(); + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnetServer/appsettings.Development.json b/Source/MQTTnet.Server/appsettings.Development.json similarity index 100% rename from Source/MQTTnetServer/appsettings.Development.json rename to Source/MQTTnet.Server/appsettings.Development.json diff --git a/Source/MQTTnetServer/appsettings.json b/Source/MQTTnet.Server/appsettings.json similarity index 100% rename from Source/MQTTnetServer/appsettings.json rename to Source/MQTTnet.Server/appsettings.json diff --git a/Source/MQTTnet/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs index 3f1e1acb..64725e26 100644 --- a/Source/MQTTnet/MqttFactory.cs +++ b/Source/MQTTnet/MqttFactory.cs @@ -10,6 +10,17 @@ namespace MQTTnet { public class MqttFactory : IMqttClientFactory, IMqttServerFactory { + private readonly IMqttNetLogger _logger; + + public MqttFactory() : this(new MqttNetLogger()) + { + } + + public MqttFactory(IMqttNetLogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + public IMqttClient CreateMqttClient() { return CreateMqttClient(new MqttNetLogger()); @@ -57,5 +68,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, _logger.CreateChildLogger()); + } } } \ No newline at end of file diff --git a/Source/MQTTnetServer/MQTTnetServer.xml b/Source/MQTTnetServer/MQTTnetServer.xml deleted file mode 100644 index 762bc400..00000000 --- a/Source/MQTTnetServer/MQTTnetServer.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - MQTTnetServer - - - - - Main Entry point - - - - - Main - - - - - - Configure and Start Kestrel - - - - - - - Read Application Settings - - - - - - Listen Entry Settings Model - - - - - Constructor - - - - - Listen Address - - - - - Listen Port - - - - - Protocol Type - - - - - Listen Protocol Types - - - - - HTTP - - - - - HTTPS - - - - - MQTT - - - - - Main Settings Model - - - - - Listen Settings - - - - - Web App Startup - - - - - Constructor - - - - - - Application Settings - - - - - This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - - - - - - - This method gets called by the runtime. Use this method to add services to the container. - - - - - diff --git a/Source/MQTTnetServer/Program.cs b/Source/MQTTnetServer/Program.cs deleted file mode 100644 index cdcb35d9..00000000 --- a/Source/MQTTnetServer/Program.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using MQTTnet.AspNetCore; -using MQTTnetServer.Settings; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; - -namespace MQTTnetServer -{ - /// - /// Main Entry point - /// - public class Program - { - /// - /// Main - /// - /// - public static void Main(string[] args) - { - try - { - CreateWebHostBuilder(args).Build().Run(); - } - catch (FileNotFoundException e) - { - Console.WriteLine("Could not find application settings file in: " + e.FileName); - return; - } - } - - /// - /// Configure and Start Kestrel - /// - /// - /// - public static IWebHostBuilder CreateWebHostBuilder(string[] args) - { - var webHost = WebHost.CreateDefaultBuilder(args); - var listen = ReadListenSettings(); - webHost - .UseKestrel(o => - { - if (listen?.Length > 0) - { - foreach (var item in listen) - { - if (item.Address?.Trim() == "*") - { - if (item.Protocol == ListenProtocolTypes.MQTT) - { - o.ListenAnyIP(item.Port, c => c.UseMqtt()); - } - else - { - o.ListenAnyIP(item.Port); - } - } - else if (item.Address?.Trim() == "localhost") - { - if (item.Protocol == ListenProtocolTypes.MQTT) - { - o.ListenLocalhost(item.Port, c => c.UseMqtt()); - } - else - { - o.ListenLocalhost(item.Port); - } - } - else - { - if (IPAddress.TryParse(item.Address, out var ip)) - { - if (item.Protocol == ListenProtocolTypes.MQTT) - { - o.Listen(ip, item.Port, c => c.UseMqtt()); - } - else - { - o.Listen(ip, item.Port); - } - } - } - } - } - else - { - o.ListenAnyIP(1883, l => l.UseMqtt()); - o.ListenAnyIP(5000); - } - }); - - webHost.UseStartup(); - - return webHost; - } - - /// - /// Read Application Settings - /// - /// - public static ListenModel[] ReadListenSettings() - { - var builder = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var listen = new List(); - builder.Bind("MQTTnetServer:Listen", listen); - - return listen.ToArray(); - } - } -} diff --git a/Source/MQTTnetServer/Startup.cs b/Source/MQTTnetServer/Startup.cs deleted file mode 100644 index 4c027568..00000000 --- a/Source/MQTTnetServer/Startup.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace MQTTnetServer -{ - /// - /// Web App Startup - /// - public class Startup - { - /// - /// Constructor - /// - /// - public Startup(IConfiguration configuration) - { - var builder = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables(); - Configuration = builder.Build(); - } - - /// - /// Application Settings - /// - public IConfigurationRoot Configuration { get; } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - /// - /// - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseMvc(); - } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - /// - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); - } - } -} \ No newline at end of file From dffc27c1f4579920a9f4c893c810ef447bc6b6c6 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 5 Mar 2019 21:37:30 +0100 Subject: [PATCH 03/17] Migrate events in server to async handlers. --- Source/MQTTnet/Server/IMqttServer.cs | 11 +++- .../IMqttServerClientConnectedHandler.cs | 9 +++ .../IMqttServerClientDisconnectedHandler.cs | 9 +++ Source/MQTTnet/Server/IMqttServerFactory.cs | 2 + Source/MQTTnet/Server/MqttClientConnection.cs | 56 ----------------- Source/MQTTnet/Server/MqttClientSession.cs | 63 +++++++++++++++++++ Source/MQTTnet/Server/MqttServer.cs | 9 ++- ... => MqttServerClientConnectedEventArgs.cs} | 4 +- ...qttServerClientConnectedHandlerDelegate.cs | 31 +++++++++ ... MqttServerClientDisconnectedEventArgs.cs} | 4 +- ...ServerClientDisconnectedHandlerDelegate.cs | 31 +++++++++ .../Server/MqttServerEventDispatcher.cs | 8 +-- 12 files changed, 169 insertions(+), 68 deletions(-) create mode 100644 Source/MQTTnet/Server/IMqttServerClientConnectedHandler.cs create mode 100644 Source/MQTTnet/Server/IMqttServerClientDisconnectedHandler.cs create mode 100644 Source/MQTTnet/Server/MqttClientSession.cs rename Source/MQTTnet/Server/{MqttClientConnectedEventArgs.cs => MqttServerClientConnectedEventArgs.cs} (61%) create mode 100644 Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs rename Source/MQTTnet/Server/{MqttClientDisconnectedEventArgs.cs => MqttServerClientDisconnectedEventArgs.cs} (64%) create mode 100644 Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index aceceeee..0286720c 100644 --- a/Source/MQTTnet/Server/IMqttServer.cs +++ b/Source/MQTTnet/Server/IMqttServer.cs @@ -10,9 +10,16 @@ namespace MQTTnet.Server event EventHandler Started; event EventHandler Stopped; - event EventHandler ClientConnected; - event EventHandler ClientDisconnected; + IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } + [Obsolete("Use ClientConnectedHandler instead.")] + event EventHandler ClientConnected; + + IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } + [Obsolete("Use ClientDisconnectedHandler instead.")] + event EventHandler ClientDisconnected; + event EventHandler ClientSubscribedTopic; + event EventHandler ClientUnsubscribedTopic; 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/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/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index fa1d6a05..0610bb9e 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(); 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/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 4d36e4cf..a0a9d442 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -50,9 +50,14 @@ namespace MQTTnet.Server public event EventHandler Started; public event EventHandler Stopped; - public event EventHandler ClientConnected; - public event EventHandler ClientDisconnected; + public IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } + public event EventHandler ClientConnected; + + public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } + public event EventHandler ClientDisconnected; + public event EventHandler ClientSubscribedTopic; + public event EventHandler ClientUnsubscribedTopic; public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } 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..58477449 --- /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 = context => + { + handler(context); + 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..45c46d99 --- /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 = context => + { + handler(context); + 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/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index 03ed93d5..4daa9941 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -8,9 +8,9 @@ namespace MQTTnet.Server public event EventHandler ClientUnsubscribedTopic; - public event EventHandler ClientConnected; + public event EventHandler ClientConnected; - public event EventHandler ClientDisconnected; + public event EventHandler ClientDisconnected; public event EventHandler ApplicationMessageReceived; @@ -26,7 +26,7 @@ namespace MQTTnet.Server public void OnClientDisconnected(string clientId, MqttClientDisconnectType disconnectType) { - ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientId, disconnectType)); + ClientDisconnected?.Invoke(this, new MqttServerClientDisconnectedEventArgs(clientId, disconnectType)); } public void OnApplicationMessageReceived(string senderClientId, MqttApplicationMessage applicationMessage) @@ -36,7 +36,7 @@ namespace MQTTnet.Server public void OnClientConnected(string clientId) { - ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientId)); + ClientConnected?.Invoke(this, new MqttServerClientConnectedEventArgs(clientId)); } } } From 87086094981bdaaea389792a062360b943e69287 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Wed, 6 Mar 2019 22:42:00 +0100 Subject: [PATCH 04/17] Add more function mappings for MQTTnet Server. --- .../MqttConnectionHandler.cs | 6 +- .../MqttWebSocketServerAdapter.cs | 4 +- .../IManagedMqttClient.cs | 7 +- .../ManagedMqttClient.cs | 55 ++++-------- .../ManagedMqttClientExtensions.cs | 12 +-- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 11 ++- .../MQTTnet.Server/Mqtt/CustomMqttFactory.cs | 32 +++++++ .../Mqtt/MqttApplicationMessageInterceptor.cs | 20 ++++- .../Mqtt/MqttClientConnectedHandler.cs | 39 +++++++++ .../Mqtt/MqttClientDisconnectedHandler.cs | 40 +++++++++ .../Mqtt/MqttClientSubscribedTopicHandler.cs | 41 +++++++++ .../MqttClientUnsubscribedTopicHandler.cs | 40 +++++++++ .../Mqtt/MqttConnectionValidator.cs | 2 +- .../MQTTnet.Server/Mqtt/MqttServerService.cs | 87 ++++++++++++------- .../Mqtt/MqttSubscriptionInterceptor.cs | 2 +- Source/MQTTnet.Server/Program.cs | 7 +- .../Scripting/PythonScriptHostService.cs | 6 +- Source/MQTTnet.Server/Scripts/00_sample.py | 33 +++++++ Source/MQTTnet.Server/Startup.cs | 12 ++- Source/MQTTnet/Adapter/IMqttServerAdapter.cs | 2 +- Source/MQTTnet/Client/IMqttClient.cs | 8 +- Source/MQTTnet/Client/MqttClient.cs | 19 +--- Source/MQTTnet/Client/MqttClientExtensions.cs | 16 ++-- .../IMqttApplicationMessageHandler.cs | 2 +- .../MqttApplicationMessageHandlerContext.cs | 15 ---- .../MqttApplicationMessageHandlerDelegate.cs | 8 +- Source/MQTTnet/IApplicationMessageReceiver.cs | 8 +- .../MqttTcpServerAdapter.Uwp.cs | 4 +- .../Implementations/MqttTcpServerAdapter.cs | 18 ++-- .../Implementations/MqttTcpServerListener.cs | 4 +- Source/MQTTnet/Server/IMqttServer.cs | 11 +-- ...IMqttServerClientSubscribedTopicHandler.cs | 9 ++ ...qttServerClientUnsubscribedTopicHandler.cs | 9 ++ .../Server/MqttClientSessionsManager.cs | 6 +- .../Server/MqttClientSubscriptionsManager.cs | 15 ++-- Source/MQTTnet/Server/MqttServer.cs | 60 ++++++------- ...qttServerClientConnectedHandlerDelegate.cs | 4 +- ...ServerClientDisconnectedHandlerDelegate.cs | 4 +- ...ttServerClientSubscribedTopicEventArgs.cs} | 4 +- ...verClientSubscribedTopicHandlerDelegate.cs | 31 +++++++ ...ServerClientUnsubscribedTopicEventArgs.cs} | 4 +- ...rClientUnsubscribedTopicHandlerDelegate.cs | 31 +++++++ .../Server/MqttServerEventDispatcher.cs | 63 ++++++++++---- .../MqttTcpChannelBenchmark.cs | 3 +- .../MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs | 5 +- .../Mockups/TestMqttServerAdapter.cs | 4 +- .../Mockups/TestServerExtensions.cs | 38 -------- Tests/MQTTnet.Core.Tests/MqttClientTests.cs | 37 ++++---- .../MQTTnet.Core.Tests/RoundtripTimeTests.cs | 5 +- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 65 +++++++------- Tests/MQTTnet.TestApp.NetCore/ClientTest.cs | 15 ++-- .../ManagedClientTest.cs | 5 +- .../PublicBrokerTest.cs | 3 +- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 9 +- .../MainPage.xaml.cs | 27 +++--- 55 files changed, 664 insertions(+), 363 deletions(-) create mode 100644 Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttClientConnectedHandler.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs create mode 100644 Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs delete mode 100644 Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerContext.cs create mode 100644 Source/MQTTnet/Server/IMqttServerClientSubscribedTopicHandler.cs create mode 100644 Source/MQTTnet/Server/IMqttServerClientUnsubscribedTopicHandler.cs rename Source/MQTTnet/Server/{MqttClientSubscribedTopicEventArgs.cs => MqttServerClientSubscribedTopicEventArgs.cs} (67%) create mode 100644 Source/MQTTnet/Server/MqttServerClientSubscribedTopicHandlerDelegate.cs rename Source/MQTTnet/Server/{MqttClientUnSubscribedTopicEventArgs.cs => MqttServerClientUnsubscribedTopicEventArgs.cs} (67%) create mode 100644 Source/MQTTnet/Server/MqttServerClientUnsubscribedTopicHandlerDelegate.cs delete mode 100644 Tests/MQTTnet.Core.Tests/Mockups/TestServerExtensions.cs 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 41039b53..ac77f166 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -11,7 +11,7 @@ namespace MQTTnet.AspNetCore { public class MqttWebSocketServerAdapter : IMqttServerAdapter { - public event EventHandler ClientAccepted; + public Action ClientAcceptedHandler { get; set; } public Task StartAsync(IMqttServerOptions options) { @@ -30,7 +30,7 @@ namespace MQTTnet.AspNetCore var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket, endpoint), new MqttPacketFormatterAdapter(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter))); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); - ClientAccepted?.Invoke(this, eventArgs); + ClientAcceptedHandler?.Invoke(eventArgs); if (eventArgs.SessionTask != null) { diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs index 644104f5..6de908fd 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -14,13 +14,8 @@ 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; diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs index 54750a11..51e8accc 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,20 +45,24 @@ 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 IMqttClientDisconnectedHandler DisconnectedHandler + { + get => _mqttClient.DisconnectedHandler; + set => _mqttClient.DisconnectedHandler = value; + } + + public IMqttApplicationMessageHandler ApplicationMessageReceivedHandler + { + get => _mqttClient.ApplicationMessageReceivedHandler; + set => _mqttClient.ApplicationMessageReceivedHandler = value; } - public event EventHandler ApplicationMessageReceived; public event EventHandler ApplicationMessageProcessed; public event EventHandler ApplicationMessageSkipped; @@ -420,25 +417,7 @@ namespace MQTTnet.Extensions.ManagedClient 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..518042c3 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 MqttApplicationMessageHandlerDelegate(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 MqttApplicationMessageHandlerDelegate(handler); return client; } diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index ebf66ec1..69d3ac84 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; +using MQTTnet.Client.Receiving; using MQTTnet.Exceptions; using MQTTnet.Protocol; @@ -18,7 +19,7 @@ namespace MQTTnet.Extensions.Rpc { _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); - _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; + _mqttClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(HandleReceivedApplicationMessageAsync); } public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -102,19 +103,21 @@ namespace MQTTnet.Extensions.Rpc } } - private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) + private Task HandleReceivedApplicationMessageAsync(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() 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 index 4af2a557..ab3d589c 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs @@ -10,7 +10,7 @@ namespace MQTTnet.Server.Mqtt public class MqttApplicationMessageInterceptor : IMqttServerApplicationMessageInterceptor { private readonly PythonScriptHostService _pythonScriptHostService; - private readonly ILogger _logger; + private readonly ILogger _logger; public MqttApplicationMessageInterceptor(PythonScriptHostService pythonScriptHostService, ILogger logger) { @@ -31,7 +31,7 @@ namespace MQTTnet.Server.Mqtt { "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); @@ -46,5 +46,21 @@ namespace MQTTnet.Server.Mqtt 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 index 0471b47e..d0852303 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs @@ -10,7 +10,7 @@ namespace MQTTnet.Server.Mqtt public class MqttConnectionValidator : IMqttServerConnectionValidator { private readonly PythonScriptHostService _pythonScriptHostService; - private readonly ILogger _logger; + private readonly ILogger _logger; public MqttConnectionValidator(PythonScriptHostService pythonScriptHostService, ILogger logger) { diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 6588ab50..39651ed0 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -8,7 +8,6 @@ using MQTTnet.Adapter; using MQTTnet.AspNetCore; using MQTTnet.Implementations; using MQTTnet.Protocol; -using MQTTnet.Server.Logging; using MQTTnet.Server.Scripting; namespace MQTTnet.Server.Mqtt @@ -17,6 +16,10 @@ namespace MQTTnet.Server.Mqtt { private readonly ILogger _logger; + private readonly MqttClientConnectedHandler _mqttClientConnectedHandler; + private readonly MqttClientDisconnectedHandler _mqttClientDisconnectedHandler; + private readonly MqttClientSubscribedTopicHandler _mqttClientSubscribedTopicHandler; + private readonly MqttClientUnsubscribedTopicHandler _mqttClientUnsubscribedTopicHandler; private readonly MqttConnectionValidator _mqttConnectionValidator; private readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor; private readonly MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor; @@ -25,15 +28,22 @@ namespace MQTTnet.Server.Mqtt private readonly IMqttServer _mqttServer; public MqttServerService( - IMqttServerFactory mqttServerFactory, + CustomMqttFactory mqttFactory, MqttWebSocketServerAdapter webSocketServerAdapter, - MqttNetLoggerWrapper mqttNetLogger, + MqttClientConnectedHandler mqttClientConnectedHandler, + MqttClientDisconnectedHandler mqttClientDisconnectedHandler, + MqttClientSubscribedTopicHandler mqttClientSubscribedTopicHandler, + MqttClientUnsubscribedTopicHandler mqttClientUnsubscribedTopicHandler, MqttConnectionValidator mqttConnectionValidator, MqttSubscriptionInterceptor mqttSubscriptionInterceptor, MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, PythonScriptHostService pythonScriptHostService, ILogger logger) { + _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)); @@ -42,11 +52,11 @@ namespace MQTTnet.Server.Mqtt var adapters = new List { - new MqttTcpServerAdapter(new MqttNetChildLoggerWrapper(null, mqttNetLogger)), + new MqttTcpServerAdapter(mqttFactory.Logger.CreateChildLogger(nameof(MqttTcpServerAdapter))), webSocketServerAdapter }; - _mqttServer = mqttServerFactory.CreateMqttServer(adapters); + _mqttServer = mqttFactory.CreateMqttServer(adapters); } public void Configure() @@ -61,6 +71,11 @@ namespace MQTTnet.Server.Mqtt .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) .Build(); + _mqttServer.ClientConnectedHandler = _mqttClientConnectedHandler; + _mqttServer.ClientDisconnectedHandler = _mqttClientDisconnectedHandler; + _mqttServer.ClientSubscribedTopicHandler = _mqttClientSubscribedTopicHandler; + _mqttServer.ClientUnsubscribedTopicHandler = _mqttClientUnsubscribedTopicHandler; + _mqttServer.StartAsync(options).GetAwaiter().GetResult(); _logger.LogInformation("MQTT server started."); @@ -68,34 +83,48 @@ namespace MQTTnet.Server.Mqtt private void Publish(PythonDictionary parameters) { - 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); - var binaryPayload = new byte[0]; - - if (payload is string stringPayload) + try { - binaryPayload = Encoding.UTF8.GetBytes(stringPayload); + 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(); } - else if (payload is ByteArray byteArray) + catch (Exception exception) { - binaryPayload = byteArray.ToArray(); + _logger.LogError(exception, "Error while publishing application message from server."); } - else if (payload is IEnumerable intArray) - { - binaryPayload = intArray.Select(Convert.ToByte).ToArray(); - } - - applicationMessageBuilder = applicationMessageBuilder - .WithPayload(binaryPayload); - - var applicationMessage = applicationMessageBuilder.Build(); - - _mqttServer.PublishAsync(applicationMessage).GetAwaiter().GetResult(); - _logger.LogInformation($"Published topic '{applicationMessage.Topic}' from server."); } } } diff --git a/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs index 115e0ab8..2d37f747 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Server.Mqtt public class MqttSubscriptionInterceptor : IMqttServerSubscriptionInterceptor { private readonly PythonScriptHostService _pythonScriptHostService; - private readonly ILogger _logger; + private readonly ILogger _logger; public MqttSubscriptionInterceptor(PythonScriptHostService pythonScriptHostService, ILogger logger) { diff --git a/Source/MQTTnet.Server/Program.cs b/Source/MQTTnet.Server/Program.cs index a9b3be67..5d50030b 100644 --- a/Source/MQTTnet.Server/Program.cs +++ b/Source/MQTTnet.Server/Program.cs @@ -48,8 +48,11 @@ namespace MQTTnet.Server Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(@" - Version: 1.0.0-alpha1 - License: MIT (read LICENSE file) + 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; diff --git a/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs b/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs index 4e8aff9b..1acc58c4 100644 --- a/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs +++ b/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs @@ -36,12 +36,12 @@ namespace MQTTnet.Server.Scripting } } - public void RegisterProxyObject(string name, object action) + public void RegisterProxyObject(string name, object @object) { if (name == null) throw new ArgumentNullException(nameof(name)); - if (action == null) throw new ArgumentNullException(nameof(action)); + if (@object == null) throw new ArgumentNullException(nameof(@object)); - _proxyObjects.Add(name, action); + _proxyObjects.Add(name, @object); } public void InvokeOptionalFunction(string name, object parameters) diff --git a/Source/MQTTnet.Server/Scripts/00_sample.py b/Source/MQTTnet.Server/Scripts/00_sample.py index 51763d89..845a2164 100644 --- a/Source/MQTTnet.Server/Scripts/00_sample.py +++ b/Source/MQTTnet.Server/Scripts/00_sample.py @@ -48,6 +48,7 @@ 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: @@ -72,3 +73,35 @@ def on_intercept_application_message(context): 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/Startup.cs b/Source/MQTTnet.Server/Startup.cs index ad7f40ea..14763d23 100644 --- a/Source/MQTTnet.Server/Startup.cs +++ b/Source/MQTTnet.Server/Startup.cs @@ -50,20 +50,24 @@ namespace MQTTnet.Server public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 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(); services.AddSingleton(); services.AddSingleton(); - - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } } } \ No newline at end of file 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/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..75d6836f 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 IMqttApplicationMessageHandler 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)); } } @@ -587,13 +576,11 @@ namespace MQTTnet.Client { 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)); + new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage)); } return Task.FromResult(0); diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index eacdbf4d..9814f1cb 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 MqttApplicationMessageHandlerDelegate(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 MqttApplicationMessageHandlerDelegate(handler); return client; } - public static IMqttClient UseReceivedApplicationMessageHandler(this IMqttClient client, IMqttApplicationMessageHandler handler) + public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, IMqttApplicationMessageHandler 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 index fdc5bf6a..5490bb04 100644 --- a/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs +++ b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs @@ -4,6 +4,6 @@ namespace MQTTnet.Client.Receiving { public interface IMqttApplicationMessageHandler { - Task HandleApplicationMessageAsync(MqttApplicationMessageHandlerContext context); + Task HandleApplicationMessageAsync(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 index cde89443..399640de 100644 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs @@ -5,9 +5,9 @@ namespace MQTTnet.Client.Receiving { public class MqttApplicationMessageHandlerDelegate : IMqttApplicationMessageHandler { - private readonly Func _handler; + private readonly Func _handler; - public MqttApplicationMessageHandlerDelegate(Action handler) + public MqttApplicationMessageHandlerDelegate(Action handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); @@ -18,12 +18,12 @@ namespace MQTTnet.Client.Receiving }; } - public MqttApplicationMessageHandlerDelegate(Func handler) + public MqttApplicationMessageHandlerDelegate(Func handler) { _handler = handler ?? throw new ArgumentNullException(nameof(handler)); } - public Task HandleApplicationMessageAsync(MqttApplicationMessageHandlerContext context) + public Task HandleApplicationMessageAsync(MqttApplicationMessageReceivedEventArgs context) { return _handler(context); } diff --git a/Source/MQTTnet/IApplicationMessageReceiver.cs b/Source/MQTTnet/IApplicationMessageReceiver.cs index 806bb757..f34222e1 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; + IMqttApplicationMessageHandler ApplicationMessageReceivedHandler { get; set; } } } 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/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index 0286720c..1186368a 100644 --- a/Source/MQTTnet/Server/IMqttServer.cs +++ b/Source/MQTTnet/Server/IMqttServer.cs @@ -11,16 +11,9 @@ namespace MQTTnet.Server event EventHandler Stopped; IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } - [Obsolete("Use ClientConnectedHandler instead.")] - event EventHandler ClientConnected; - IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } - [Obsolete("Use ClientDisconnectedHandler instead.")] - event EventHandler ClientDisconnected; - - event EventHandler ClientSubscribedTopic; - - event EventHandler ClientUnsubscribedTopic; + IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get; set; } + IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get; set; } IMqttServerOptions Options { get; } 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/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index e0ce7e4e..423638a4 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -196,7 +196,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 +258,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 +288,7 @@ namespace MQTTnet.Server await TryCleanupChannelAsync(channelAdapter).ConfigureAwait(false); - _eventDispatcher.OnClientDisconnected(clientId, disconnectType); + await _eventDispatcher.HandleClientDisconnectedAsync(clientId, disconnectType).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/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index a0a9d442..0707dec8 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -28,40 +28,40 @@ 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 IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } - public event EventHandler ClientConnected; + public IMqttServerClientConnectedHandler ClientConnectedHandler + { + get => _eventDispatcher.ClientConnectedHandler; + set => _eventDispatcher.ClientConnectedHandler = value; + } - public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } - public event EventHandler ClientDisconnected; + public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler + { + get => _eventDispatcher.ClientDisconnectedHandler; + set => _eventDispatcher.ClientDisconnectedHandler = value; + } + + public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler + { + get => _eventDispatcher.ClientSubscribedTopicHandler; + set => _eventDispatcher.ClientSubscribedTopicHandler = value; + } - public event EventHandler ClientSubscribedTopic; - - public event EventHandler ClientUnsubscribedTopic; - - public IMqttApplicationMessageHandler ReceivedApplicationMessageHandler { get; set; } - public event EventHandler ApplicationMessageReceived; + public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler + { + get => _eventDispatcher.ClientUnsubscribedTopicHandler; + set => _eventDispatcher.ClientUnsubscribedTopicHandler = value; + } + + public IMqttApplicationMessageHandler ApplicationMessageReceivedHandler + { + get => _eventDispatcher.ApplicationMessageReceivedHandler; + set => _eventDispatcher.ApplicationMessageReceivedHandler = value; + } public IMqttServerOptions Options { get; private set; } @@ -123,7 +123,7 @@ namespace MQTTnet.Server foreach (var adapter in _adapters) { - adapter.ClientAccepted += OnClientAccepted; + adapter.ClientAcceptedHandler = OnClientAccepted; await adapter.StartAsync(Options).ConfigureAwait(false); } @@ -146,7 +146,7 @@ namespace MQTTnet.Server foreach (var adapter in _adapters) { - adapter.ClientAccepted -= OnClientAccepted; + adapter.ClientAcceptedHandler = null; await adapter.StopAsync().ConfigureAwait(false); } @@ -170,7 +170,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/MqttServerClientConnectedHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs index 58477449..2883d66b 100644 --- a/Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs +++ b/Source/MQTTnet/Server/MqttServerClientConnectedHandlerDelegate.cs @@ -11,9 +11,9 @@ namespace MQTTnet.Server { if (handler == null) throw new ArgumentNullException(nameof(handler)); - _handler = context => + _handler = eventArgs => { - handler(context); + handler(eventArgs); return Task.FromResult(0); }; } diff --git a/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs b/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs index 45c46d99..b1d495a6 100644 --- a/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs +++ b/Source/MQTTnet/Server/MqttServerClientDisconnectedHandlerDelegate.cs @@ -11,9 +11,9 @@ namespace MQTTnet.Server { if (handler == null) throw new ArgumentNullException(nameof(handler)); - _handler = context => + _handler = eventArgs => { - handler(context); + handler(eventArgs); return Task.FromResult(0); }; } 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 4daa9941..cea18e1d 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 IMqttApplicationMessageHandler 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 MqttServerClientDisconnectedEventArgs(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 MqttServerClientConnectedEventArgs(clientId)); + var handler = ApplicationMessageReceivedHandler; + if (handler == null) + { + return Task.FromResult(0); + } + + return handler.HandleApplicationMessageAsync(new MqttApplicationMessageReceivedEventArgs(senderClientId, applicationMessage)); } } } 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.Core.Tests/MQTTv5/Client_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs index 6c13dbbc..08f0fc96 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 MqttApplicationMessageHandlerDelegate(e => { lock (receivedMessages) { receivedMessages.Add(e); } - }; + }); await client1.SubscribeAsync("a"); 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/RoundtripTimeTests.cs b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs index c8de9e40..3d88a442 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 MqttApplicationMessageHandlerDelegate(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..93ad66b2 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 MqttApplicationMessageHandlerDelegate(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.NetCore/ClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs index 8343d9e7..f85a42c9 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 MqttApplicationMessageHandlerDelegate(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..7498e9ef 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 MqttApplicationMessageHandlerDelegate(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..00f80c62 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 MqttApplicationMessageHandlerDelegate(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..73aea2d7 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 MqttApplicationMessageHandlerDelegate(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}"; From 03bfc5f00b598ae9c9e8332cd102b3fa8677508e Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 8 Mar 2019 23:47:51 +0100 Subject: [PATCH 05/17] Refactor event handler namings. --- README.md | 13 +++- ...licationMessageProcessedHandlerDelegate.cs | 31 +++++++++ ...pplicationMessageSkippedHandlerDelegate.cs | 31 +++++++++ .../ConnectingFailedHandlerDelegate.cs | 31 +++++++++ .../IApplicationMessageProcessedHandler.cs | 9 +++ .../IApplicationMessageSkippedHandler.cs | 9 +++ .../IConnectingFailedHandler.cs | 9 +++ .../IManagedMqttClient.cs | 12 ++-- .../IManagedMqttClientOptions.cs | 1 - ...SynchronizingSubscriptionsFailedHandler.cs | 9 +++ .../ManagedMqttClient.cs | 68 +++++++++++++------ .../ManagedMqttClientExtensions.cs | 4 +- ...gs.cs => ManagedProcessFailedEventArgs.cs} | 4 +- .../MqttFactoryExtensions.cs | 7 +- ...izingSubscriptionsFailedHandlerDelegate.cs | 31 +++++++++ .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 19 ++++-- ...cAwareApplicationMessageReceivedHandler.cs | 31 +++++++++ Source/MQTTnet/Client/MqttClient.cs | 4 +- Source/MQTTnet/Client/MqttClientExtensions.cs | 6 +- .../IMqttApplicationMessageHandler.cs | 9 --- .../IMqttApplicationMessageReceivedHandler.cs | 9 +++ ...licationMessageReceivedHandlerDelegate.cs} | 8 +-- Source/MQTTnet/IApplicationMessageReceiver.cs | 2 +- Source/MQTTnet/IMqttFactory.cs | 11 +++ Source/MQTTnet/MqttFactory.cs | 17 +++-- Source/MQTTnet/Server/IMqttServer.cs | 7 +- .../Server/IMqttServerStartedHandler.cs | 10 +++ .../Server/IMqttServerStoppedHandler.cs | 10 +++ Source/MQTTnet/Server/MqttClientConnection.cs | 2 +- .../Server/MqttClientSessionsManager.cs | 7 +- Source/MQTTnet/Server/MqttServer.cs | 21 ++++-- .../Server/MqttServerEventDispatcher.cs | 4 +- .../MqttServerStartedHandlerDelegate.cs | 31 +++++++++ .../MqttServerStoppedHandlerDelegate.cs | 31 +++++++++ Tests/MQTTnet.Core.Tests/MQTTnet.Tests.csproj | 1 + .../MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs | 2 +- Tests/MQTTnet.Core.Tests/MqttFactoryTests.cs | 2 +- Tests/MQTTnet.Core.Tests/RPC_Tests.cs | 55 +++++++++++++++ .../MQTTnet.Core.Tests/RoundtripTimeTests.cs | 2 +- Tests/MQTTnet.Core.Tests/Server_Tests.cs | 2 +- Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs | 5 +- Tests/MQTTnet.TestApp.NetCore/ClientTest.cs | 2 +- .../ManagedClientTest.cs | 2 +- .../PublicBrokerTest.cs | 2 +- Tests/MQTTnet.TestApp.NetCore/ServerTest.cs | 2 +- 45 files changed, 489 insertions(+), 96 deletions(-) create mode 100644 Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageProcessedHandlerDelegate.cs create mode 100644 Source/MQTTnet.Extensions.ManagedClient/ApplicationMessageSkippedHandlerDelegate.cs create mode 100644 Source/MQTTnet.Extensions.ManagedClient/ConnectingFailedHandlerDelegate.cs create mode 100644 Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageProcessedHandler.cs create mode 100644 Source/MQTTnet.Extensions.ManagedClient/IApplicationMessageSkippedHandler.cs create mode 100644 Source/MQTTnet.Extensions.ManagedClient/IConnectingFailedHandler.cs create mode 100644 Source/MQTTnet.Extensions.ManagedClient/ISynchronizingSubscriptionsFailedHandler.cs rename Source/MQTTnet.Extensions.ManagedClient/{MqttManagedProcessFailedEventArgs.cs => ManagedProcessFailedEventArgs.cs} (57%) create mode 100644 Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs create mode 100644 Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs delete mode 100644 Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs create mode 100644 Source/MQTTnet/Client/Receiving/IMqttApplicationMessageReceivedHandler.cs rename Source/MQTTnet/Client/Receiving/{MqttApplicationMessageHandlerDelegate.cs => MqttApplicationMessageReceivedHandlerDelegate.cs} (56%) create mode 100644 Source/MQTTnet/IMqttFactory.cs create mode 100644 Source/MQTTnet/Server/IMqttServerStartedHandler.cs create mode 100644 Source/MQTTnet/Server/IMqttServerStoppedHandler.cs create mode 100644 Source/MQTTnet/Server/MqttServerStartedHandlerDelegate.cs create mode 100644 Source/MQTTnet/Server/MqttServerStoppedHandlerDelegate.cs create mode 100644 Tests/MQTTnet.Core.Tests/RPC_Tests.cs diff --git a/README.md b/README.md index b74c1fbe..410f6d8b 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.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 6de908fd..9919081f 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs @@ -15,13 +15,13 @@ namespace MQTTnet.Extensions.ManagedClient IMqttClientConnectedHandler ConnectedHandler { get; set; } IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } + + IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; } + IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; } + + IConnectingFailedHandler ConnectingFailedHandler { get; set; } + ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; } - event EventHandler ApplicationMessageProcessed; - event EventHandler ApplicationMessageSkipped; - - event EventHandler ConnectingFailed; - event EventHandler SynchronizingSubscriptionsFailed; - 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 51e8accc..7340578e 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs @@ -57,17 +57,19 @@ namespace MQTTnet.Extensions.ManagedClient set => _mqttClient.DisconnectedHandler = value; } - public IMqttApplicationMessageHandler ApplicationMessageReceivedHandler + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get => _mqttClient.ApplicationMessageReceivedHandler; set => _mqttClient.ApplicationMessageReceivedHandler = value; } - public event EventHandler ApplicationMessageProcessed; - public event EventHandler ApplicationMessageSkipped; + public IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; } - public event EventHandler ConnectingFailed; - public event EventHandler SynchronizingSubscriptionsFailed; + public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; } + + public IConnectingFailedHandler ConnectingFailedHandler { get; set; } + + public ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; } public async Task StartAsync(IManagedMqttClientOptions options) { @@ -126,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) @@ -352,7 +371,7 @@ namespace MQTTnet.Extensions.ManagedClient } finally { - ApplicationMessageProcessed?.Invoke(this, new ApplicationMessageProcessedEventArgs(message, transmitException)); + ApplicationMessageProcessedHandler?.HandleApplicationMessageProcessedAsync(new ApplicationMessageProcessedEventArgs(message, transmitException)).GetAwaiter().GetResult(); } } @@ -395,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); + } } } @@ -413,7 +436,12 @@ 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; } } diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs index 518042c3..a0be4b10 100644 --- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs +++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs @@ -68,7 +68,7 @@ namespace MQTTnet.Extensions.ManagedClient return client; } - client.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } @@ -81,7 +81,7 @@ namespace MQTTnet.Extensions.ManagedClient return client; } - client.ApplicationMessageReceivedHandler = 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 69d3ac84..8a279c75 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -4,7 +4,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MQTTnet.Client; -using MQTTnet.Client.Receiving; using MQTTnet.Exceptions; using MQTTnet.Protocol; @@ -14,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.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(HandleReceivedApplicationMessageAsync); + _applicationMessageReceivedHandler = new RpcAwareApplicationMessageReceivedHandler( + mqttClient.ApplicationMessageReceivedHandler, + HandleApplicationMessageReceivedAsync); + + _mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler; } public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -46,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"; @@ -103,7 +112,7 @@ namespace MQTTnet.Extensions.Rpc } } - private Task HandleReceivedApplicationMessageAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + private Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) { if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs)) { @@ -122,9 +131,11 @@ namespace MQTTnet.Extensions.Rpc 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/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 75d6836f..45b99c88 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -48,7 +48,7 @@ namespace MQTTnet.Client public IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } - public IMqttApplicationMessageHandler ApplicationMessageReceivedHandler { get; set; } + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; } public bool IsConnected { get; private set; } @@ -579,7 +579,7 @@ namespace MQTTnet.Client var handler = ApplicationMessageReceivedHandler; if (handler != null) { - return handler.HandleApplicationMessageAsync( + return handler.HandleApplicationMessageReceivedAsync( new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage)); } diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index 9814f1cb..a44eb42a 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -71,7 +71,7 @@ namespace MQTTnet.Client return client; } - client.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } @@ -84,12 +84,12 @@ namespace MQTTnet.Client return client; } - client.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(handler); + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(handler); return client; } - public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, IMqttApplicationMessageHandler handler) + public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, IMqttApplicationMessageReceivedHandler handler) { client.ApplicationMessageReceivedHandler = handler; diff --git a/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs b/Source/MQTTnet/Client/Receiving/IMqttApplicationMessageHandler.cs deleted file mode 100644 index 5490bb04..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(MqttApplicationMessageReceivedEventArgs eventArgs); - } -} 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/MqttApplicationMessageHandlerDelegate.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs similarity index 56% rename from Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs rename to Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs index 399640de..1d73aaa1 100644 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageHandlerDelegate.cs +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs @@ -3,11 +3,11 @@ using System.Threading.Tasks; namespace MQTTnet.Client.Receiving { - public class MqttApplicationMessageHandlerDelegate : IMqttApplicationMessageHandler + public class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler { private readonly Func _handler; - public MqttApplicationMessageHandlerDelegate(Action handler) + public MqttApplicationMessageReceivedHandlerDelegate(Action handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); @@ -18,12 +18,12 @@ namespace MQTTnet.Client.Receiving }; } - public MqttApplicationMessageHandlerDelegate(Func handler) + public MqttApplicationMessageReceivedHandlerDelegate(Func handler) { _handler = handler ?? throw new ArgumentNullException(nameof(handler)); } - public Task HandleApplicationMessageAsync(MqttApplicationMessageReceivedEventArgs context) + public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs context) { return _handler(context); } diff --git a/Source/MQTTnet/IApplicationMessageReceiver.cs b/Source/MQTTnet/IApplicationMessageReceiver.cs index f34222e1..4e474856 100644 --- a/Source/MQTTnet/IApplicationMessageReceiver.cs +++ b/Source/MQTTnet/IApplicationMessageReceiver.cs @@ -4,6 +4,6 @@ namespace MQTTnet { public interface IApplicationMessageReceiver { - IMqttApplicationMessageHandler ApplicationMessageReceivedHandler { get; set; } + 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/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs index 64725e26..ab6cc760 100644 --- a/Source/MQTTnet/MqttFactory.cs +++ b/Source/MQTTnet/MqttFactory.cs @@ -8,22 +8,22 @@ using MQTTnet.Server; namespace MQTTnet { - public class MqttFactory : IMqttClientFactory, IMqttServerFactory + public class MqttFactory : IMqttFactory { - private readonly IMqttNetLogger _logger; - public MqttFactory() : this(new MqttNetLogger()) { } public MqttFactory(IMqttNetLogger logger) { - _logger = logger ?? throw new ArgumentNullException(nameof(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) @@ -37,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) @@ -50,8 +50,7 @@ namespace MQTTnet public IMqttServer CreateMqttServer() { - var logger = new MqttNetLogger(); - return CreateMqttServer(logger); + return CreateMqttServer(DefaultLogger); } public IMqttServer CreateMqttServer(IMqttNetLogger logger) @@ -73,7 +72,7 @@ namespace MQTTnet { if (adapters == null) throw new ArgumentNullException(nameof(adapters)); - return new MqttServer(adapters, _logger.CreateChildLogger()); + return new MqttServer(adapters, DefaultLogger.CreateChildLogger()); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/IMqttServer.cs b/Source/MQTTnet/Server/IMqttServer.cs index 1186368a..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,8 +6,8 @@ namespace MQTTnet.Server { public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher { - event EventHandler Started; - event EventHandler Stopped; + IMqttServerStartedHandler StartedHandler { get; set; } + IMqttServerStoppedHandler StoppedHandler { get; set; } IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; } IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; } 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 0610bb9e..4981b06a 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -369,7 +369,7 @@ namespace MQTTnet.Server //// } ////}); - await Task.FromResult(0); + //await Task.FromResult(0); } private async Task SendPendingPacketsAsync() diff --git a/Source/MQTTnet/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 423638a4..34393130 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)) @@ -313,8 +312,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/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs index 0707dec8..c3b970c4 100644 --- a/Source/MQTTnet/Server/MqttServer.cs +++ b/Source/MQTTnet/Server/MqttServer.cs @@ -30,8 +30,9 @@ namespace MQTTnet.Server _logger = logger.CreateChildLogger(nameof(MqttServer)); } - public event EventHandler Started; - public event EventHandler Stopped; + public IMqttServerStartedHandler StartedHandler { get; set; } + + public IMqttServerStoppedHandler StoppedHandler { get; set; } public IMqttServerClientConnectedHandler ClientConnectedHandler { @@ -57,7 +58,7 @@ namespace MQTTnet.Server set => _eventDispatcher.ClientUnsubscribedTopicHandler = value; } - public IMqttApplicationMessageHandler ApplicationMessageReceivedHandler + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get => _eventDispatcher.ApplicationMessageReceivedHandler; set => _eventDispatcher.ApplicationMessageReceivedHandler = value; @@ -128,7 +129,12 @@ namespace MQTTnet.Server } _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() @@ -151,7 +157,6 @@ namespace MQTTnet.Server } _logger.Info("Stopped."); - Stopped?.Invoke(this, EventArgs.Empty); } finally { @@ -163,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() diff --git a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs index cea18e1d..c85805d8 100644 --- a/Source/MQTTnet/Server/MqttServerEventDispatcher.cs +++ b/Source/MQTTnet/Server/MqttServerEventDispatcher.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Server public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get; set; } - public IMqttApplicationMessageHandler ApplicationMessageReceivedHandler { get; set; } + public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; } public Task HandleClientConnectedAsync(string clientId) { @@ -67,7 +67,7 @@ namespace MQTTnet.Server return Task.FromResult(0); } - return handler.HandleApplicationMessageAsync(new MqttApplicationMessageReceivedEventArgs(senderClientId, applicationMessage)); + return handler.HandleApplicationMessageReceivedAsync(new MqttApplicationMessageReceivedEventArgs(senderClientId, applicationMessage)); } } } 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.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 08f0fc96..93fc2a60 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs @@ -214,7 +214,7 @@ 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.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(e => + client1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { lock (receivedMessages) { 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 3d88a442..4cd29cef 100644 --- a/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs +++ b/Tests/MQTTnet.Core.Tests/RoundtripTimeTests.cs @@ -30,7 +30,7 @@ namespace MQTTnet.Tests TaskCompletionSource response = null; - receiverClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(args => + receiverClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(args => { response?.SetResult(args.ApplicationMessage.ConvertPayloadToString()); }); diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index 93ad66b2..5dbde0e6 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -68,7 +68,7 @@ namespace MQTTnet.Tests var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); var c1 = await testEnvironment.ConnectClientAsync(); - c1.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(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); 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.NetCore/ClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs index f85a42c9..09f85777 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ClientTest.cs @@ -28,7 +28,7 @@ namespace MQTTnet.TestApp.NetCore } }; - client.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(e => + client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs index 7498e9ef..0d4f8f9d 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -35,7 +35,7 @@ namespace MQTTnet.TestApp.NetCore try { var managedClient = new MqttFactory().CreateManagedMqttClient(); - managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(e => + managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); }); diff --git a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs index 00f80c62..7f322130 100644 --- a/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PublicBrokerTest.cs @@ -81,7 +81,7 @@ namespace MQTTnet.TestApp.NetCore var topic = Guid.NewGuid().ToString(); MqttApplicationMessage receivedMessage = null; - client.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(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 73aea2d7..bd626719 100644 --- a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -79,7 +79,7 @@ namespace MQTTnet.TestApp.NetCore var mqttServer = new MqttFactory().CreateMqttServer(); - mqttServer.ApplicationMessageReceivedHandler = new MqttApplicationMessageHandlerDelegate(e => + mqttServer.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { MqttNetConsoleLogger.PrintToConsole( $"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", From 0d95dc563cf13e0fa08e1526992080f900fc678a Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 8 Mar 2019 23:48:07 +0100 Subject: [PATCH 06/17] Update documents. --- Build/MQTTnet.nuspec | 8 ++++++++ README.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 2a8a763f..2f12dd05 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -12,12 +12,20 @@ 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!). +* [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/README.md b/README.md index 410f6d8b..9ae1bb84 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ 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 _MQTTnet Server_ is a reference implementation of a MQTT server using this library. It has the following features. * Running portable (no installation required) From 461bc6e594ade9c43cd10f9d16348a23b6946072 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 8 Mar 2019 23:48:28 +0100 Subject: [PATCH 07/17] Add MQTTnet.Server to NoUWP solution. --- MQTTnet.noUWP.sln | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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} From 6e64d653c5f775c37c951e35452b8fdccc86ccd4 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Fri, 8 Mar 2019 23:48:49 +0100 Subject: [PATCH 08/17] Hide connect password from logs. --- Source/MQTTnet/Packets/MqttConnectPacket.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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, "]"); } } } From 9070087fadbba816afa665aead43bfd56467c966 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 10 Mar 2019 09:51:49 +0100 Subject: [PATCH 09/17] Add flag for indicating if application message processing has failed. --- Source/MQTTnet/Client/MqttClient.cs | 37 ++++++++++--------- ...MqttApplicationMessageReceivedEventArgs.cs | 2 + 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 45b99c88..a09f6b53 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -510,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 { @@ -572,18 +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); var handler = ApplicationMessageReceivedHandler; if (handler != null) { - return handler.HandleApplicationMessageReceivedAsync( - new MqttApplicationMessageReceivedEventArgs(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/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; } } } From bef0860e53bccc471b4609418e023cd56708f8fb Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 10 Mar 2019 12:10:11 +0100 Subject: [PATCH 10/17] Add support for WebSocket connections. --- Source/MQTTnet.Server/MQTTnet.Server.csproj | 22 ++++++++ Source/MQTTnet.Server/Startup.cs | 40 +++++++++++++- Source/MQTTnet.Server/package.json | 12 +++++ Source/MQTTnet.Server/tsconfig.json | 12 +++++ Source/MQTTnet.Server/wwwroot/Index.html | 35 +++++++++++++ Source/MQTTnet.Server/wwwroot/app/app.ts | 58 +++++++++++++++++++++ 6 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 Source/MQTTnet.Server/package.json create mode 100644 Source/MQTTnet.Server/tsconfig.json create mode 100644 Source/MQTTnet.Server/wwwroot/Index.html create mode 100644 Source/MQTTnet.Server/wwwroot/app/app.ts diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj index f142e237..3e5d224b 100644 --- a/Source/MQTTnet.Server/MQTTnet.Server.csproj +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -14,6 +14,7 @@ false false latest + 3.3 @@ -26,10 +27,23 @@ 1701;1702 + + + + Always + + Always + + + Always + + + Always + @@ -37,6 +51,8 @@ + + @@ -58,4 +74,10 @@ + + + Always + + + diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs index 14763d23..e04e8480 100644 --- a/Source/MQTTnet.Server/Startup.cs +++ b/Source/MQTTnet.Server/Startup.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Builder; +using System; +using System.Net.WebSockets; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -29,7 +31,8 @@ namespace MQTTnet.Server IHostingEnvironment environment, MqttServerService mqttServerService, PythonScriptHostService pythonScriptHostService, - DataSharingService dataSharingService) + DataSharingService dataSharingService, + MqttWebSocketServerAdapter mqttWebSocketServerAdapter) { if (environment.IsDevelopment()) { @@ -40,9 +43,42 @@ namespace MQTTnet.Server 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 mqttWebSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, + $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}").ConfigureAwait(false); + } + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next().ConfigureAwait(false); + } + }); + dataSharingService.Configure(); pythonScriptHostService.Configure(); mqttServerService.Configure(); 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 From 9e7c70e6498efb9dc4f4c22e672cc1c89cc5cb3e Mon Sep 17 00:00:00 2001 From: Kiarash Date: Sun, 10 Mar 2019 13:58:18 +0100 Subject: [PATCH 11/17] Add new configuartion model Added new models for review --- .../Configuration/EndpointConfiguration.cs | 9 -- .../Configuration/ListenModel.cs | 136 +++++++++++++++++- .../Configuration/ListenProtocolTypes.cs | 23 --- .../MqttNetServerConfiguration.cs | 7 - .../Configuration/SettingsModel.cs | 35 ++++- .../MQTTnet.Server/Mqtt/MqttServerService.cs | 76 ++++++++-- Source/MQTTnet.Server/Startup.cs | 14 +- Source/MQTTnet.Server/appsettings.json | 61 ++++---- 8 files changed, 270 insertions(+), 91 deletions(-) delete mode 100644 Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs delete mode 100644 Source/MQTTnet.Server/Configuration/ListenProtocolTypes.cs delete mode 100644 Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs diff --git a/Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs b/Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs deleted file mode 100644 index b4b04961..00000000 --- a/Source/MQTTnet.Server/Configuration/EndpointConfiguration.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MQTTnet.Server.Configuration -{ - public class EndpointConfiguration - { - public int Port { get; set; } = 1883; - - public int BacklogSize { get; set; } = 10; - } -} diff --git a/Source/MQTTnet.Server/Configuration/ListenModel.cs b/Source/MQTTnet.Server/Configuration/ListenModel.cs index bf62a687..1715312d 100644 --- a/Source/MQTTnet.Server/Configuration/ListenModel.cs +++ b/Source/MQTTnet.Server/Configuration/ListenModel.cs @@ -1,4 +1,7 @@ -namespace MQTTnet.Server.Configuration +using System.IO; +using System.Net; + +namespace MQTTnet.Server.Configuration { /// /// Listen Entry Settings Model @@ -12,19 +15,142 @@ { } + /// + /// Path to Certificate + /// + public string CertificatePath { get; set; } + + /// + /// Enabled / Disable + /// + public bool Enabled { get; set; } = true; + /// /// Listen Address /// - public string Address { get; set; } + public string IPv4 { get; set; } + + /// + /// Listen Address + /// + public string IPv6 { get; set; } /// /// Listen Port /// - public int Port { get; set; } + public int Port { get; set; } = 1883; /// - /// Protocol Type + /// Read Certificate file /// - public ListenProtocolTypes Protocol { get; set; } = ListenProtocolTypes.HTTP; + /// + 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 IPAddress ReafIPv4() + { + if (IPv4 == "*") + { + return IPAddress.Parse("0.0.0.0"); + } + + if (IPv4 == "localhost") + { + return IPAddress.Parse("127.0.0.1"); + } + + if (IPAddress.TryParse(IPv4, out var ip)) + { + return ip; + } + + throw new System.Exception($"Could not parse IPv4 address: {IPv4}"); + } + + /// + /// Read IPv4 + /// + /// + public bool TryReadIPv4(out IPAddress address) + { + if (IPv4 == "*") + { + address = IPAddress.Parse("::"); + return true; + } + + if (IPv4 == "localhost") + { + address = IPAddress.Parse("::1"); + return true; + } + + if (IPv4 == "disable") + { + address = null; + return false; + } + + 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 = null; + return false; + } + + 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/ListenProtocolTypes.cs b/Source/MQTTnet.Server/Configuration/ListenProtocolTypes.cs deleted file mode 100644 index 84a0a48b..00000000 --- a/Source/MQTTnet.Server/Configuration/ListenProtocolTypes.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MQTTnet.Server.Configuration -{ - /// - /// Listen Protocol Types - /// - public enum ListenProtocolTypes - { - /// - /// HTTP - /// - HTTP = 0, - - /// - /// HTTPS - /// - HTTPS = 1, - - /// - /// MQTT - /// - MQTT = 20 - } -} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs b/Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs deleted file mode 100644 index 632b8d10..00000000 --- a/Source/MQTTnet.Server/Configuration/MqttNetServerConfiguration.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MQTTnet.Server.Configuration -{ - public class MqttNetServerConfiguration - { - - } -} diff --git a/Source/MQTTnet.Server/Configuration/SettingsModel.cs b/Source/MQTTnet.Server/Configuration/SettingsModel.cs index e3930848..47332dc4 100644 --- a/Source/MQTTnet.Server/Configuration/SettingsModel.cs +++ b/Source/MQTTnet.Server/Configuration/SettingsModel.cs @@ -1,15 +1,42 @@ -using System.Collections.Generic; - -namespace MQTTnet.Server.Configuration +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 IEnumerable Listen { get; set; } + public ListenModel Listen { get; set; } = new ListenModel(); + + /// + /// Encryption Listen Settings + /// + public ListenModel ListenEncryption { 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/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 39651ed0..046876dc 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -16,18 +16,19 @@ namespace MQTTnet.Server.Mqtt { 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 MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor; private readonly PythonScriptHostService _pythonScriptHostService; - private readonly IMqttServer _mqttServer; - public MqttServerService( + Configuration.SettingsModel settings, CustomMqttFactory mqttFactory, MqttWebSocketServerAdapter webSocketServerAdapter, MqttClientConnectedHandler mqttClientConnectedHandler, @@ -40,6 +41,7 @@ namespace MQTTnet.Server.Mqtt 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)); @@ -64,19 +66,75 @@ namespace MQTTnet.Server.Mqtt _pythonScriptHostService.RegisterProxyObject("publish", new Action(Publish)); var options = new MqttServerOptionsBuilder() - .WithDefaultEndpoint() - .WithDefaultEndpointPort(1883) + .WithMaxPendingMessagesPerClient(_settings.MaxPendingMessagesPerClient) + .WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(_settings.CommunicationTimeout)) .WithConnectionValidator(_mqttConnectionValidator) .WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) - .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) - .Build(); + .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor); + + // Configure unencrypted connections + if (_settings.Listen.Enabled) + { + options.WithDefaultEndpoint(); + if (_settings.Listen.TryReadIPv6(out var address4)) + { + options.WithDefaultEndpointBoundIPAddress(address4); + } + + if (_settings.Listen.TryReadIPv6(out var address6)) + { + options.WithDefaultEndpointBoundIPV6Address(address6); + } + + if (_settings.Listen.Port > 0) + { + options.WithDefaultEndpointPort(_settings.Listen.Port); + } + } + else + { + options.WithoutDefaultEndpoint(); + } + + // Configure encrypted connections + if (_settings.ListenEncryption.Enabled) + { + options + .WithEncryptedEndpoint() + .WithEncryptionSslProtocol(System.Security.Authentication.SslProtocols.Tls12) + .WithEncryptionCertificate(_settings.ListenEncryption.ReadCertificate()); + + if (_settings.ListenEncryption.TryReadIPv6(out var address4)) + { + options.WithDefaultEndpointBoundIPAddress(address4); + } + + if (_settings.ListenEncryption.TryReadIPv6(out var address6)) + { + options.WithDefaultEndpointBoundIPV6Address(address6); + } + + if (_settings.Listen.Port > 0) + { + options.WithEncryptedEndpointPort(_settings.ListenEncryption.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).GetAwaiter().GetResult(); + _mqttServer.StartAsync(options.Build()).GetAwaiter().GetResult(); _logger.LogInformation("MQTT server started."); } @@ -127,4 +185,4 @@ namespace MQTTnet.Server.Mqtt } } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs index 14763d23..c4eb45a9 100644 --- a/Source/MQTTnet.Server/Startup.cs +++ b/Source/MQTTnet.Server/Startup.cs @@ -15,11 +15,11 @@ namespace MQTTnet.Server { public Startup(IConfiguration configuration) { - //var builder = new ConfigurationBuilder() - // .AddJsonFile("appsettings.json") - // .AddEnvironmentVariables(); + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables(); - //Configuration = builder.Build(); + Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } @@ -52,6 +52,12 @@ namespace MQTTnet.Server { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + // Read settings + var settings = new Configuration.SettingsModel(); + Configuration.Bind("MQTTnet", settings); + services.AddSingleton(settings); + + // Wire up dependencies services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Source/MQTTnet.Server/appsettings.json b/Source/MQTTnet.Server/appsettings.json index 0bf2d9e3..f5e5a5ec 100644 --- a/Source/MQTTnet.Server/appsettings.json +++ b/Source/MQTTnet.Server/appsettings.json @@ -1,32 +1,33 @@ { - "MQTTnetServer": { - /* - Wildcard Addresses: - * - All local IP addresses - localhost - Localhost only - - Supported Protocols: - 0 - HTTP - 1 - HTTPS - 20 - MQTT - */ - "Listen": [ - { - "Address": "localhost", - "Port": 5000, - "Protocol": 0 - }, - { - "Address": "*", - "Port": 1883, - "Protocol": 20 - } - ] - }, - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "AllowedHosts": "*" + "MQTTnet": { + /* + Wildcard Addresses: + * - All local IP addresses + localhost - Localhost only + disable - Skip address assignment + */ + "Listen": { + "Enabled": true, + "IPv4": "localhost", + "IPv6": "localhost", + "Port": 1883 + }, + "ListenEncryption": { + "Enabled": false, + "IPv4": "localhost", + "IPv6": "localhost", + "Port": 8883, + "CertificatePath": "/absolute/path/to/certificate" + }, + "CommunicationTimeout": 15, /* In seconds */ + "ConnectionBacklog": 0, /* Set 0 to disable */ + "EnablePersistentSessions": false, + "MaxPendingMessagesPerClient": 250 + }, + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" } From 2ca6866c5e89742e39df00efd4124a9589227fd2 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 10 Mar 2019 14:55:17 +0100 Subject: [PATCH 12/17] Remove obsolete code. --- Source/MQTTnet.Server/Program.cs | 69 -------------------------------- 1 file changed, 69 deletions(-) diff --git a/Source/MQTTnet.Server/Program.cs b/Source/MQTTnet.Server/Program.cs index 5d50030b..b721a143 100644 --- a/Source/MQTTnet.Server/Program.cs +++ b/Source/MQTTnet.Server/Program.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using MQTTnet.Server.Configuration; namespace MQTTnet.Server { @@ -72,75 +69,9 @@ namespace MQTTnet.Server }); }); - //var listen = ReadListenSettings(); - //webHost - // .UseKestrel(o => - // { - // if (listen?.Length > 0) - // { - // foreach (var item in listen) - // { - // if (item.Address?.Trim() == "*") - // { - // if (item.Protocol == ListenProtocolTypes.MQTT) - // { - // o.ListenAnyIP(item.Port, c => c.UseMqtt()); - // } - // else - // { - // o.ListenAnyIP(item.Port); - // } - // } - // else if (item.Address?.Trim() == "localhost") - // { - // if (item.Protocol == ListenProtocolTypes.MQTT) - // { - // o.ListenLocalhost(item.Port, c => c.UseMqtt()); - // } - // else - // { - // o.ListenLocalhost(item.Port); - // } - // } - // else - // { - // if (IPAddress.TryParse(item.Address, out var ip)) - // { - // if (item.Protocol == ListenProtocolTypes.MQTT) - // { - // o.Listen(ip, item.Port, c => c.UseMqtt()); - // } - // else - // { - // o.Listen(ip, item.Port); - // } - // } - // } - // } - // } - // else - // { - // o.ListenAnyIP(1883, l => l.UseMqtt()); - // o.ListenAnyIP(5000); - // } - // }); - webHost.UseStartup(); return webHost; } - - public static ListenModel[] ReadListenSettings() - { - var builder = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var listen = new List(); - builder.Bind("MQTTnetServer:Listen", listen); - - return listen.ToArray(); - } } } From 0c056b511efcfb40e4a35664eba5f50935b04dcd Mon Sep 17 00:00:00 2001 From: Kiarash Date: Sun, 10 Mar 2019 15:56:11 +0100 Subject: [PATCH 13/17] Fix and clean up --- .../Configuration/ListenModel.cs | 28 +---- .../Configuration/SettingsModel.cs | 4 +- .../MQTTnet.Server/Mqtt/MqttServerService.cs | 26 ++--- Source/MQTTnet.Server/Program.cs | 103 ++---------------- Source/MQTTnet.Server/Startup.cs | 2 +- Source/MQTTnet.Server/appsettings.json | 6 +- 6 files changed, 32 insertions(+), 137 deletions(-) diff --git a/Source/MQTTnet.Server/Configuration/ListenModel.cs b/Source/MQTTnet.Server/Configuration/ListenModel.cs index 1715312d..e72d8975 100644 --- a/Source/MQTTnet.Server/Configuration/ListenModel.cs +++ b/Source/MQTTnet.Server/Configuration/ListenModel.cs @@ -59,30 +59,6 @@ namespace MQTTnet.Server.Configuration return File.ReadAllBytes(CertificatePath); } - /// - /// Read IPv4 - /// - /// - public IPAddress ReafIPv4() - { - if (IPv4 == "*") - { - return IPAddress.Parse("0.0.0.0"); - } - - if (IPv4 == "localhost") - { - return IPAddress.Parse("127.0.0.1"); - } - - if (IPAddress.TryParse(IPv4, out var ip)) - { - return ip; - } - - throw new System.Exception($"Could not parse IPv4 address: {IPv4}"); - } - /// /// Read IPv4 /// @@ -91,13 +67,13 @@ namespace MQTTnet.Server.Configuration { if (IPv4 == "*") { - address = IPAddress.Parse("::"); + address = IPAddress.Parse("0.0.0.0"); return true; } if (IPv4 == "localhost") { - address = IPAddress.Parse("::1"); + address = IPAddress.Parse("127.0.0.1"); return true; } diff --git a/Source/MQTTnet.Server/Configuration/SettingsModel.cs b/Source/MQTTnet.Server/Configuration/SettingsModel.cs index 47332dc4..6b3c95c2 100644 --- a/Source/MQTTnet.Server/Configuration/SettingsModel.cs +++ b/Source/MQTTnet.Server/Configuration/SettingsModel.cs @@ -27,12 +27,12 @@ /// /// Listen Settings /// - public ListenModel Listen { get; set; } = new ListenModel(); + public ListenModel Endpoint { get; set; } = new ListenModel(); /// /// Encryption Listen Settings /// - public ListenModel ListenEncryption { get; set; } = new ListenModel(); + public ListenModel EndpointEncryption { get; set; } = new ListenModel(); /// /// Set limit for max pending messages per client diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 046876dc..c3abcf0c 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -73,22 +73,22 @@ namespace MQTTnet.Server.Mqtt .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor); // Configure unencrypted connections - if (_settings.Listen.Enabled) + if (_settings.Endpoint.Enabled) { options.WithDefaultEndpoint(); - if (_settings.Listen.TryReadIPv6(out var address4)) + if (_settings.Endpoint.TryReadIPv4(out var address4)) { options.WithDefaultEndpointBoundIPAddress(address4); } - if (_settings.Listen.TryReadIPv6(out var address6)) + if (_settings.Endpoint.TryReadIPv6(out var address6)) { options.WithDefaultEndpointBoundIPV6Address(address6); } - if (_settings.Listen.Port > 0) + if (_settings.Endpoint.Port > 0) { - options.WithDefaultEndpointPort(_settings.Listen.Port); + options.WithDefaultEndpointPort(_settings.Endpoint.Port); } } else @@ -97,26 +97,26 @@ namespace MQTTnet.Server.Mqtt } // Configure encrypted connections - if (_settings.ListenEncryption.Enabled) + if (_settings.EndpointEncryption.Enabled) { options .WithEncryptedEndpoint() .WithEncryptionSslProtocol(System.Security.Authentication.SslProtocols.Tls12) - .WithEncryptionCertificate(_settings.ListenEncryption.ReadCertificate()); + .WithEncryptionCertificate(_settings.EndpointEncryption.ReadCertificate()); - if (_settings.ListenEncryption.TryReadIPv6(out var address4)) + if (_settings.EndpointEncryption.TryReadIPv4(out var address4)) { - options.WithDefaultEndpointBoundIPAddress(address4); + options.WithEncryptedEndpointBoundIPAddress(address4); } - if (_settings.ListenEncryption.TryReadIPv6(out var address6)) + if (_settings.EndpointEncryption.TryReadIPv6(out var address6)) { - options.WithDefaultEndpointBoundIPV6Address(address6); + options.WithEncryptedEndpointBoundIPAddress(address6); } - if (_settings.Listen.Port > 0) + if (_settings.Endpoint.Port > 0) { - options.WithEncryptedEndpointPort(_settings.ListenEncryption.Port); + options.WithEncryptedEndpointPort(_settings.EndpointEncryption.Port); } } else diff --git a/Source/MQTTnet.Server/Program.cs b/Source/MQTTnet.Server/Program.cs index 5d50030b..221f48c9 100644 --- a/Source/MQTTnet.Server/Program.cs +++ b/Source/MQTTnet.Server/Program.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using MQTTnet.Server.Configuration; namespace MQTTnet.Server { @@ -25,21 +22,25 @@ namespace MQTTnet.Server } } + private static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel() + .UseStartup(); + private static void PrintLogo() { Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(@" - __ __ ____ _______ _______ _ _____ - | \/ |/ __ \__ __|__ __| | | / ____| - | \ / | | | | | | | |_ __ ___| |_ | (___ ___ _ ____ _____ _ __ + __ __ ____ _______ _______ _ _____ + | \/ |/ __ \__ __|__ __| | | / ____| + | \ / | | | | | | | |_ __ ___| |_ | (___ ___ _ ____ _____ _ __ | |\/| | | | | | | | | '_ \ / _ \ __| \___ \ / _ \ '__\ \ / / _ \ '__| - | | | | |__| | | | | | | | | __/ |_ ____) | __/ | \ V / __/ | + | | | | |__| | | | | | | | | __/ |_ ____) | __/ | \ V / __/ | |_| |_|\___\_\ |_| |_|_| |_|\___|\__| |_____/ \___|_| \_/ \___|_|"); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(@" - -- The official MQTT server implementation of MQTTnet -- + -- The official MQTT server implementation of MQTTnet -- Copyright (c) 2017-2019 The MQTTnet Team"); Console.ForegroundColor = ConsoleColor.Blue; @@ -60,87 +61,5 @@ namespace MQTTnet.Server Console.WriteLine(" ! THIS IS AN ALPHA VERSION! IT IS NOT RECOMMENDED TO USE IT FOR ANY DIFFERENT PURPOSE THAN TESTING OR EVALUATING!"); Console.WriteLine(); } - - private static IWebHostBuilder CreateWebHostBuilder(string[] args) - { - var webHost = WebHost.CreateDefaultBuilder(args) - .UseKestrel(kestrelOptions => - { - kestrelOptions.ListenAnyIP(80, listenOptions => - { - listenOptions.NoDelay = true; - }); - }); - - //var listen = ReadListenSettings(); - //webHost - // .UseKestrel(o => - // { - // if (listen?.Length > 0) - // { - // foreach (var item in listen) - // { - // if (item.Address?.Trim() == "*") - // { - // if (item.Protocol == ListenProtocolTypes.MQTT) - // { - // o.ListenAnyIP(item.Port, c => c.UseMqtt()); - // } - // else - // { - // o.ListenAnyIP(item.Port); - // } - // } - // else if (item.Address?.Trim() == "localhost") - // { - // if (item.Protocol == ListenProtocolTypes.MQTT) - // { - // o.ListenLocalhost(item.Port, c => c.UseMqtt()); - // } - // else - // { - // o.ListenLocalhost(item.Port); - // } - // } - // else - // { - // if (IPAddress.TryParse(item.Address, out var ip)) - // { - // if (item.Protocol == ListenProtocolTypes.MQTT) - // { - // o.Listen(ip, item.Port, c => c.UseMqtt()); - // } - // else - // { - // o.Listen(ip, item.Port); - // } - // } - // } - // } - // } - // else - // { - // o.ListenAnyIP(1883, l => l.UseMqtt()); - // o.ListenAnyIP(5000); - // } - // }); - - webHost.UseStartup(); - - return webHost; - } - - public static ListenModel[] ReadListenSettings() - { - var builder = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .Build(); - - var listen = new List(); - builder.Bind("MQTTnetServer:Listen", listen); - - return listen.ToArray(); - } } -} +} \ No newline at end of file diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs index bdcc7aaf..659f5057 100644 --- a/Source/MQTTnet.Server/Startup.cs +++ b/Source/MQTTnet.Server/Startup.cs @@ -90,7 +90,7 @@ namespace MQTTnet.Server // Read settings var settings = new Configuration.SettingsModel(); - Configuration.Bind("MQTTnet", settings); + Configuration.Bind("MQTT", settings); services.AddSingleton(settings); // Wire up dependencies diff --git a/Source/MQTTnet.Server/appsettings.json b/Source/MQTTnet.Server/appsettings.json index f5e5a5ec..1b563e36 100644 --- a/Source/MQTTnet.Server/appsettings.json +++ b/Source/MQTTnet.Server/appsettings.json @@ -1,18 +1,18 @@ { - "MQTTnet": { + "MQTT": { /* Wildcard Addresses: * - All local IP addresses localhost - Localhost only disable - Skip address assignment */ - "Listen": { + "Endpoint": { "Enabled": true, "IPv4": "localhost", "IPv6": "localhost", "Port": 1883 }, - "ListenEncryption": { + "EndpointEncryption": { "Enabled": false, "IPv4": "localhost", "IPv6": "localhost", From 510c3bd0bfd1e2e83152b381d360bfcc6f6d2091 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 10 Mar 2019 15:58:55 +0100 Subject: [PATCH 14/17] Move logger into WebSocketServerAdapter instead of creating a new instance. --- .../ApplicationBuilderExtensions.cs | 3 +-- .../MqttWebSocketServerAdapter.cs | 20 +++++++++++++++++-- Source/MQTTnet.Server/MQTTnet.Server.csproj | 8 ++++---- .../MQTTnet.Server/Mqtt/MqttServerService.cs | 9 ++++++--- Source/MQTTnet.Server/Startup.cs | 9 ++------- 5 files changed, 31 insertions(+), 18 deletions(-) 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/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index ac77f166..e5720e7a 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,6 +12,15 @@ namespace MQTTnet.AspNetCore { public class MqttWebSocketServerAdapter : IMqttServerAdapter { + 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,11 +33,17 @@ 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 channel = new MqttWebSocketChannel(webSocket, endpoint); + var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(), _logger.CreateChildLogger(nameof(MqttWebSocketServerAdapter))); + + var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false); + var isSecureConnection = clientCertificate != null; + clientCertificate?.Dispose(); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); ClientAcceptedHandler?.Invoke(eventArgs); diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj index 3e5d224b..26ba48aa 100644 --- a/Source/MQTTnet.Server/MQTTnet.Server.csproj +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -36,13 +36,13 @@ Always - Always + Never - Always + Never - Always + Never @@ -76,7 +76,7 @@ - Always + Never diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 046876dc..b40a2b12 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -30,7 +30,6 @@ namespace MQTTnet.Server.Mqtt public MqttServerService( Configuration.SettingsModel settings, CustomMqttFactory mqttFactory, - MqttWebSocketServerAdapter webSocketServerAdapter, MqttClientConnectedHandler mqttClientConnectedHandler, MqttClientDisconnectedHandler mqttClientDisconnectedHandler, MqttClientSubscribedTopicHandler mqttClientSubscribedTopicHandler, @@ -52,15 +51,19 @@ namespace MQTTnet.Server.Mqtt _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(nameof(MqttTcpServerAdapter))), - webSocketServerAdapter + new MqttTcpServerAdapter(mqttFactory.Logger.CreateChildLogger()), + WebSocketServerAdapter }; _mqttServer = mqttFactory.CreateMqttServer(adapters); } + public MqttWebSocketServerAdapter WebSocketServerAdapter { get; } + public void Configure() { _pythonScriptHostService.RegisterProxyObject("publish", new Action(Publish)); diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs index bdcc7aaf..af6f01d5 100644 --- a/Source/MQTTnet.Server/Startup.cs +++ b/Source/MQTTnet.Server/Startup.cs @@ -1,11 +1,9 @@ using System; -using System.Net.WebSockets; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using MQTTnet.AspNetCore; using MQTTnet.Server.Logging; using MQTTnet.Server.Mqtt; using MQTTnet.Server.Scripting; @@ -31,8 +29,7 @@ namespace MQTTnet.Server IHostingEnvironment environment, MqttServerService mqttServerService, PythonScriptHostService pythonScriptHostService, - DataSharingService dataSharingService, - MqttWebSocketServerAdapter mqttWebSocketServerAdapter) + DataSharingService dataSharingService) { if (environment.IsDevelopment()) { @@ -64,8 +61,7 @@ namespace MQTTnet.Server { using (var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false)) { - await mqttWebSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, - $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}").ConfigureAwait(false); + await mqttServerService.WebSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, context).ConfigureAwait(false); } } else @@ -99,7 +95,6 @@ namespace MQTTnet.Server services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); From 0e2c3abd056ab4eacbe1358b432a002889bd4de5 Mon Sep 17 00:00:00 2001 From: Kiarash Date: Sun, 10 Mar 2019 16:34:18 +0100 Subject: [PATCH 15/17] Add missing method for setting encrpted IPv6 address. * Clean up --- Source/MQTTnet.Server/Configuration/ListenModel.cs | 8 ++++---- Source/MQTTnet.Server/Mqtt/MqttServerService.cs | 2 +- Source/MQTTnet.Server/appsettings.json | 2 +- Source/MQTTnet/Server/MqttServerOptionsBuilder.cs | 6 ++++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/MQTTnet.Server/Configuration/ListenModel.cs b/Source/MQTTnet.Server/Configuration/ListenModel.cs index e72d8975..52c5de42 100644 --- a/Source/MQTTnet.Server/Configuration/ListenModel.cs +++ b/Source/MQTTnet.Server/Configuration/ListenModel.cs @@ -79,8 +79,8 @@ namespace MQTTnet.Server.Configuration if (IPv4 == "disable") { - address = null; - return false; + address = IPAddress.None; + return true; } if (IPAddress.TryParse(IPv4, out var ip)) @@ -114,8 +114,8 @@ namespace MQTTnet.Server.Configuration if (IPv6 == "disable") { - address = null; - return false; + address = IPAddress.None; + return true; } if (IPAddress.TryParse(IPv6, out var ip)) diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 7a5d7577..bac16868 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -114,7 +114,7 @@ namespace MQTTnet.Server.Mqtt if (_settings.EndpointEncryption.TryReadIPv6(out var address6)) { - options.WithEncryptedEndpointBoundIPAddress(address6); + options.WithEncryptedEndpointBoundIPV6Address(address6); } if (_settings.Endpoint.Port > 0) diff --git a/Source/MQTTnet.Server/appsettings.json b/Source/MQTTnet.Server/appsettings.json index 1b563e36..09251354 100644 --- a/Source/MQTTnet.Server/appsettings.json +++ b/Source/MQTTnet.Server/appsettings.json @@ -17,7 +17,7 @@ "IPv4": "localhost", "IPv6": "localhost", "Port": 8883, - "CertificatePath": "/absolute/path/to/certificate" + "CertificatePath": "/absolute/path/to/pfx" }, "CommunicationTimeout": 15, /* In seconds */ "ConnectionBacklog": 0, /* Set 0 to disable */ 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; From 1e697a94a14b3a0404b4ff7599ae99fa696add70 Mon Sep 17 00:00:00 2001 From: Kiarash Date: Sun, 10 Mar 2019 17:18:20 +0100 Subject: [PATCH 16/17] Add Kesterl Settings into appsettings --- .../Configuration/SettingsModel.cs | 4 ++-- .../MQTTnet.Server/Mqtt/MqttServerService.cs | 22 +++++++++---------- Source/MQTTnet.Server/Program.cs | 2 ++ Source/MQTTnet.Server/appsettings.json | 14 ++++++++++-- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Source/MQTTnet.Server/Configuration/SettingsModel.cs b/Source/MQTTnet.Server/Configuration/SettingsModel.cs index 6b3c95c2..ca1ebb47 100644 --- a/Source/MQTTnet.Server/Configuration/SettingsModel.cs +++ b/Source/MQTTnet.Server/Configuration/SettingsModel.cs @@ -27,12 +27,12 @@ /// /// Listen Settings /// - public ListenModel Endpoint { get; set; } = new ListenModel(); + public ListenModel EndPoint { get; set; } = new ListenModel(); /// /// Encryption Listen Settings /// - public ListenModel EndpointEncryption { get; set; } = new ListenModel(); + public ListenModel EndPointEncryption { get; set; } = new ListenModel(); /// /// Set limit for max pending messages per client diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index bac16868..c17b2413 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -76,22 +76,22 @@ namespace MQTTnet.Server.Mqtt .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor); // Configure unencrypted connections - if (_settings.Endpoint.Enabled) + if (_settings.EndPoint.Enabled) { options.WithDefaultEndpoint(); - if (_settings.Endpoint.TryReadIPv4(out var address4)) + if (_settings.EndPoint.TryReadIPv4(out var address4)) { options.WithDefaultEndpointBoundIPAddress(address4); } - if (_settings.Endpoint.TryReadIPv6(out var address6)) + if (_settings.EndPoint.TryReadIPv6(out var address6)) { options.WithDefaultEndpointBoundIPV6Address(address6); } - if (_settings.Endpoint.Port > 0) + if (_settings.EndPoint.Port > 0) { - options.WithDefaultEndpointPort(_settings.Endpoint.Port); + options.WithDefaultEndpointPort(_settings.EndPoint.Port); } } else @@ -100,26 +100,26 @@ namespace MQTTnet.Server.Mqtt } // Configure encrypted connections - if (_settings.EndpointEncryption.Enabled) + if (_settings.EndPointEncryption.Enabled) { options .WithEncryptedEndpoint() .WithEncryptionSslProtocol(System.Security.Authentication.SslProtocols.Tls12) - .WithEncryptionCertificate(_settings.EndpointEncryption.ReadCertificate()); + .WithEncryptionCertificate(_settings.EndPointEncryption.ReadCertificate()); - if (_settings.EndpointEncryption.TryReadIPv4(out var address4)) + if (_settings.EndPointEncryption.TryReadIPv4(out var address4)) { options.WithEncryptedEndpointBoundIPAddress(address4); } - if (_settings.EndpointEncryption.TryReadIPv6(out var address6)) + if (_settings.EndPointEncryption.TryReadIPv6(out var address6)) { options.WithEncryptedEndpointBoundIPV6Address(address6); } - if (_settings.Endpoint.Port > 0) + if (_settings.EndPoint.Port > 0) { - options.WithEncryptedEndpointPort(_settings.EndpointEncryption.Port); + options.WithEncryptedEndpointPort(_settings.EndPointEncryption.Port); } } else diff --git a/Source/MQTTnet.Server/Program.cs b/Source/MQTTnet.Server/Program.cs index 221f48c9..9161f166 100644 --- a/Source/MQTTnet.Server/Program.cs +++ b/Source/MQTTnet.Server/Program.cs @@ -29,6 +29,7 @@ namespace MQTTnet.Server private static void PrintLogo() { + Console.ResetColor(); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(@" __ __ ____ _______ _______ _ _____ @@ -60,6 +61,7 @@ namespace MQTTnet.Server 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/appsettings.json b/Source/MQTTnet.Server/appsettings.json index 09251354..c913f388 100644 --- a/Source/MQTTnet.Server/appsettings.json +++ b/Source/MQTTnet.Server/appsettings.json @@ -1,4 +1,14 @@ { + "Kestrel": { + "EndPoints": { + "Http": { + "Url": "http://localhost:80" + }, + "Https": { + "Url": "http://localhost:443" + } + } + }, "MQTT": { /* Wildcard Addresses: @@ -6,13 +16,13 @@ localhost - Localhost only disable - Skip address assignment */ - "Endpoint": { + "EndPoint": { "Enabled": true, "IPv4": "localhost", "IPv6": "localhost", "Port": 1883 }, - "EndpointEncryption": { + "EndPointEncryption": { "Enabled": false, "IPv4": "localhost", "IPv6": "localhost", From 562a5304f6efc74307417454c7f849c5cef04197 Mon Sep 17 00:00:00 2001 From: Christian Kratky Date: Sun, 10 Mar 2019 19:52:55 +0100 Subject: [PATCH 17/17] Add flag for secure connections. --- Build/MQTTnet.nuspec | 2 ++ Source/MQTTnet.AspnetCore/MqttConnectionContext.cs | 1 + Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs | 7 ++++--- Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs | 1 + Source/MQTTnet/Adapter/IMqttChannelAdapter.cs | 2 ++ Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 2 ++ Source/MQTTnet/Channel/IMqttChannel.cs | 1 + .../MQTTnet/Implementations/MqttClientAdapterFactory.cs | 1 - Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs | 4 ++++ Source/MQTTnet/Implementations/MqttTcpChannel.cs | 6 ++++++ Source/MQTTnet/Implementations/MqttWebSocketChannel.cs | 9 ++++++++- Source/MQTTnet/Internal/TestMqttChannel.cs | 2 ++ Source/MQTTnet/Server/MqttClientSessionsManager.cs | 3 ++- Source/MQTTnet/Server/MqttConnectionValidatorContext.cs | 5 ++++- Tests/MQTTnet.Benchmarks/SerializerBenchmark.cs | 4 +++- .../Mockups/TestMqttCommunicationAdapter.cs | 4 +++- 16 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index 2f12dd05..f5137d18 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -25,6 +25,8 @@ * [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/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/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index e5720e7a..959d25a4 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -38,13 +38,14 @@ namespace MQTTnet.AspNetCore if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; - var channel = new MqttWebSocketChannel(webSocket, endpoint); - var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(), _logger.CreateChildLogger(nameof(MqttWebSocketServerAdapter))); - + 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); ClientAcceptedHandler?.Invoke(eventArgs); diff --git a/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs index d0852303..83f66039 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs @@ -26,6 +26,7 @@ namespace MQTTnet.Server.Mqtt { { "client_id", context.ClientId }, { "endpoint", context.Endpoint }, + { "is_secure_connection", context.IsSecureConnection }, { "username", context.Username }, { "password", context.Password }, { "result", PythonConvert.Pythonfy(context.ReturnCode) } 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/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/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/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/Server/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/MqttClientSessionsManager.cs index 34393130..07640d94 100644 --- a/Source/MQTTnet/Server/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/MqttClientSessionsManager.cs @@ -298,7 +298,8 @@ namespace MQTTnet.Server connectPacket.Username, connectPacket.Password, connectPacket.WillMessage, - clientAdapter.Endpoint); + clientAdapter.Endpoint, + clientAdapter.IsSecureConnection); var connectionValidator = _options.ConnectionValidator; 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/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/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);