diff --git a/.gitignore b/.gitignore
index 569e43ac..93a9b60f 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/
@@ -287,8 +288,6 @@ __pycache__/
*.odx.cs
*.xsd.cs
-
-Build/nuget.exe
*.js
*.map
diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec
index 241582a2..a46cd815 100644
--- a/Build/MQTTnet.AspNetCore.nuspec
+++ b/Build/MQTTnet.AspNetCore.nuspec
@@ -5,23 +5,28 @@
0.0.0
Christian Kratky
Christian Kratky
- https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
+ LICENSE
https://github.com/chkr1011/MQTTnet
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
false
This is a support library to integrate MQTTnet into AspNetCore.
For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
- Copyright Christian Kratky 2016-2018
+ Copyright Christian Kratky 2016-2019
MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
-
+
+
+
+
+
+
diff --git a/Build/MQTTnet.Extensions.ManagedClient.nuspec b/Build/MQTTnet.Extensions.ManagedClient.nuspec
index af18c0f6..70fcd44b 100644
--- a/Build/MQTTnet.Extensions.ManagedClient.nuspec
+++ b/Build/MQTTnet.Extensions.ManagedClient.nuspec
@@ -5,20 +5,23 @@
0.0.0
Christian Kratky
Christian Kratky
- https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
+ LICENSE
https://github.com/chkr1011/MQTTnet
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
false
This is an extension library which provides a managed MQTT client with additional features using MQTTnet.
For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
- Copyright Christian Kratky 2016-2018
+ Copyright Christian Kratky 2016-2019
MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
-
+
+
+
+
@@ -37,4 +40,4 @@
-
\ No newline at end of file
+
diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec
index 2a443728..13d3d4c1 100644
--- a/Build/MQTTnet.Extensions.Rpc.nuspec
+++ b/Build/MQTTnet.Extensions.Rpc.nuspec
@@ -5,20 +5,23 @@
0.0.0
Christian Kratky
Christian Kratky
- https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
+ LICENSE
https://github.com/chkr1011/MQTTnet
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
false
This is an extension library which allows executing synchronous device calls including a response using MQTTnet.
For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
- Copyright Christian Kratky 2016-2018
+ Copyright Christian Kratky 2016-2019
MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
-
+
+
+
+
@@ -37,4 +40,4 @@
-
\ No newline at end of file
+
diff --git a/Build/MQTTnet.NETStandard.nuspec b/Build/MQTTnet.NETStandard.nuspec
new file mode 100644
index 00000000..492532dd
--- /dev/null
+++ b/Build/MQTTnet.NETStandard.nuspec
@@ -0,0 +1,42 @@
+
+
+
+ MQTTnet.NETStandard
+ 0.0.0
+ Christian Kratky
+ Christian Kratky
+ LICENSE
+ https://github.com/chkr1011/MQTTnet
+ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
+ false
+ MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). This package contains the .NET Standard version of the library only.
+ For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
+ Copyright Christian Kratky 2016-2019
+ MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec
index 8ac9e692..c2f91e42 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -5,15 +5,44 @@
0.0.0
Christian Kratky
Christian Kratky
- https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
+ LICENSE
https://github.com/chkr1011/MQTTnet
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
false
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).
- * [Core] Fixed a memory leak when processing lots of messages (thanks to @tschanko)
+
+* [Core] Added support for MQTTv5 packages.
+* [Core] Performance improvements.
+* [Core] Removed obsolete methods.
+* [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).
+* [Core] Fixed a memory leak when using SSL connections (thanks to @biovoid).
+* [Client] Added validation of topics before publishing.
+* [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!).
+* [Client] Added more configuration values to TCP endpoint options.
+* [Client] Added used PacketIdentifier to publish result.
+* [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).
+* [ManagedClient] Added validation of topics before publishing.
+* [ManagedClient] The internal MQTT client is now closed properly (thanks to @vbBerni).
+* [Server] Added support for MQTTv5 clients. The server will still return _success_ for all cases at the moment even if more granular codes are available.
+* [Server] Fixed issues in QoS 2 handling which leads to message loss.
+* [Server] Replaced all events with proper async compatible handlers (BREAKING CHANGE!).
+* [Server] The used logger instance is now propagated to the WebSocket server adapter.
+* [Server] Added the flag "IsSecureConnection" which is set to true when the connection is encrypted.
* [Server] Fixed wrong will message behavior when stopping server (thanks to @JohBa)
+* [Server] Added validation of topics before publishing.
+* [Server] Added more configuration values to TCP endpoint options.
+* [MQTTnet Server] Added as first Alpha version of standalone cross platform MQTT server.
+
+* [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.
- Copyright Christian Kratky 2016-2018
+ Copyright Christian Kratky 2016-2019
MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin
@@ -29,7 +58,7 @@
-
+
@@ -39,6 +68,9 @@
+
+
+
diff --git a/Build/build.ps1 b/Build/build.ps1
index 63bae548..3f5c487d 100644
--- a/Build/build.ps1
+++ b/Build/build.ps1
@@ -3,45 +3,66 @@ param([string]$assemblyVersion, [string]$nugetVersion)
if ([string]::IsNullOrEmpty($assemblyVersion)) {$assemblyVersion = "0.0.1"}
if ([string]::IsNullOrEmpty($nugetVersion)) {$nugetVersion = "0.0.1"}
+$vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere'
+$msbuild = &$vswhere -products * -requires Microsoft.Component.MSBuild -latest -find MSBuild\**\Bin\MSBuild.exe
+
+Write-Host
Write-Host "Assembly version = $assemblyVersion"
Write-Host "Nuget version = $nugetVersion"
+Write-Host "MSBuild path = $msbuild"
Write-Host
-$vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere'
+# Build the core library
+&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
-$path = &$vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath
-if ($path) {
- $msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe'
+# Build the ASP.NET Core 2.0 extension
+&$msbuild ..\Source\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- # Build the core library
- &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet\MQTTnet.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
-
- # Build the ASP.NET Core 2.0 extension
- &$msbuild ..\Source\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+# Build the RPC extension
+&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- # Build the RPC extension
- &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+# Build the Managed Client extension
+&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+&$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- # Build the Managed Client extension
- &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- &$msbuild ..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
+# Build MQTTnet.Server
+Remove-Item ..\Source\MQTTnet.Server\bin\Release\netcoreapp2.2 -Recurse -Force -ErrorAction SilentlyContinue
+&$msbuild ..\Source\MQTTnet.Server\MQTTnet.Server.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netcoreapp2.2" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
- Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
+$source = "..\Source\MQTTnet.Server\bin\Release\netcoreapp2.2"
+$destination = "..\Source\MQTTnet.Server\bin\MQTTnet.Server-Portable-v$nugetVersion.zip"
+If(Test-path $destination) {Remove-item $destination}
+ Add-Type -assembly "system.io.compression.filesystem"
+[io.compression.zipfile]::CreateFromDirectory($source, $destination)
- New-Item -ItemType Directory -Force -Path .\NuGet
- .\nuget.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
- .\nuget.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
- .\nuget.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
- .\nuget.exe pack MQTTnet.Extensions.ManagedClient.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
-}
\ No newline at end of file
+# Create NuGet packages.
+Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
+
+Copy-Item MQTTnet.AspNetCore.nuspec -Destination MQTTnet.AspNetCore.nuspec.old -Force
+(Get-Content MQTTnet.AspNetCore.nuspec) -replace '\$nugetVersion', $nugetVersion | Set-Content MQTTnet.AspNetCore.nuspec
+Copy-Item MQTTnet.Extensions.Rpc.nuspec -Destination MQTTnet.Extensions.Rpc.nuspec.old -Force
+(Get-Content MQTTnet.Extensions.Rpc.nuspec) -replace '\$nugetVersion', $nugetVersion | Set-Content MQTTnet.Extensions.Rpc.nuspec
+Copy-Item MQTTnet.Extensions.ManagedClient.nuspec -Destination MQTTnet.Extensions.ManagedClient.nuspec.old -Force
+(Get-Content MQTTnet.Extensions.ManagedClient.nuspec) -replace '\$nugetVersion', $nugetVersion | Set-Content MQTTnet.Extensions.ManagedClient.nuspec
+
+New-Item -ItemType Directory -Force -Path .\NuGet
+.\nuget.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
+.\nuget.exe pack MQTTnet.NETStandard.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
+.\nuget.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
+.\nuget.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
+.\nuget.exe pack MQTTnet.Extensions.ManagedClient.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
+
+Move-Item MQTTnet.AspNetCore.nuspec.old -Destination MQTTnet.AspNetCore.nuspec -Force
+Move-Item MQTTnet.Extensions.Rpc.nuspec.old -Destination MQTTnet.Extensions.Rpc.nuspec -Force
+Move-Item MQTTnet.Extensions.ManagedClient.nuspec.old -Destination MQTTnet.Extensions.ManagedClient.nuspec -Force
\ No newline at end of file
diff --git a/Build/nuget.exe b/Build/nuget.exe
new file mode 100644
index 00000000..c96819b5
Binary files /dev/null and b/Build/nuget.exe differ
diff --git a/Images/Logo_128x128.png b/Images/Logo_128x128.png
index a528d7ce..697acbf2 100644
Binary files a/Images/Logo_128x128.png and b/Images/Logo_128x128.png differ
diff --git a/LICENSE b/LICENSE
index 7d84e2b1..784616d0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 Christian Kratky
+MQTTnet Copyright (c) 2016-2019 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
diff --git a/MQTTnet.noUWP.sln b/MQTTnet.noUWP.sln
index 8d9130ed..1d24ba22 100644
--- a/MQTTnet.noUWP.sln
+++ b/MQTTnet.noUWP.sln
@@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2010
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{32A630A7-2598-41D7-B625-204CD906F5FB}"
@@ -45,6 +43,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.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
@@ -57,22 +59,6 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.Build.0 = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.ActiveCfg = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.Build.0 = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.ActiveCfg = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.Build.0 = Debug|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.Build.0 = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.ActiveCfg = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.Build.0 = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.ActiveCfg = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|Any CPU
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -201,12 +187,43 @@ 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
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|ARM.Build.0 = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x64.Build.0 = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Debug|x86.Build.0 = Debug|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|ARM.ActiveCfg = Release|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|ARM.Build.0 = Release|Any CPU
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41}.Release|x64.ActiveCfg = Release|Any CPU
+ {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
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB}
{3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
@@ -215,6 +232,8 @@ 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}
+ {9C7106CA-96B8-4ABE-B3B4-9357AB8ACB41} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
+ {DADEA836-5CC3-474C-A2D8-4D0F31118CD7} = {32A630A7-2598-41D7-B625-204CD906F5FB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
diff --git a/MQTTnet.sln b/MQTTnet.sln
index be6a0b6f..28d5dcab 100644
--- a/MQTTnet.sln
+++ b/MQTTnet.sln
@@ -1,9 +1,9 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27004.2010
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28729.10
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}"
EndProject
@@ -19,6 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1
Build\MQTTnet.AspNetCore.nuspec = Build\MQTTnet.AspNetCore.nuspec
Build\MQTTnet.Extensions.ManagedClient.nuspec = Build\MQTTnet.Extensions.ManagedClient.nuspec
Build\MQTTnet.Extensions.Rpc.nuspec = Build\MQTTnet.Extensions.Rpc.nuspec
+ Build\MQTTnet.NETStandard.nuspec = Build\MQTTnet.NETStandard.nuspec
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec
Build\upload.ps1 = Build\upload.ps1
EndProjectSection
@@ -47,6 +48,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -226,6 +229,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 +260,7 @@ Global
{998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587}
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
+ {5699FB8C-838C-4AB0-80A5-9CA809F9B65B} = {32A630A7-2598-41D7-B625-204CD906F5FB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
diff --git a/MQTTnet.sln.DotSettings b/MQTTnet.sln.DotSettings
index 023e3d60..20d5d147 100644
--- a/MQTTnet.sln.DotSettings
+++ b/MQTTnet.sln.DotSettings
@@ -7,6 +7,7 @@
True
True
True
+ True
True
True
True
diff --git a/README.md b/README.md
index 24eb1e78..b93dd2c0 100644
--- a/README.md
+++ b/README.md
@@ -20,12 +20,14 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* Async support
* TLS 1.2 support for client and server (but not UWP servers)
-* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
+* Extensible communication channels (e.g. In-Memory, TCP, TCP+TLS, WS)
* Lightweight (only the low level implementation of MQTT, no overhead)
* Performance optimized (processing ~70.000 messages / second)*
+* Uniform API across all supported versions of the MQTT protocol
* Interfaces included for mocking and testing
* Access to internal trace messages
-* Unit tested (~100 tests)
+* Unit tested (~200 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_.
@@ -48,6 +50,17 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* WebSockets supported (via ASP.NET Core 2.0, separate nuget)
* A custom message interceptor can be added which allows transforming or extending every received application message
* 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 standalone cross platform MQTT server (like mosquitto) basing on this library. It has the following features.
+* Running portable (no installation required)
+* Runs und Windows, Linux, macOS, Raspberry Pi
+* Python scripting support for manipulating messages, validation of clients, building business logic etc.
+* Supports WebSocket and TCP (with and without TLS) connections
+* Provides a HTTP based API (including Swagger endpoint)
+* Extensive configuration parameters and customization supported
## Supported frameworks
@@ -62,7 +75,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
## Supported MQTT versions
-* 5.0.0 (planned)
+* 5.0.0
* 3.1.1
* 3.1.0
@@ -93,9 +106,11 @@ This library is used in the following projects:
If you use this library and want to see your project here please create a pull request.
-## MIT License
+## License
-Copyright (c) 2017-2018 Christian Kratky
+MIT License
+
+MQTTnet Copyright (c) 2016-2019 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
diff --git a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
index 2125c7df..94fc8e41 100644
--- a/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
@@ -28,10 +28,9 @@ namespace MQTTnet.AspNetCore
}
var adapter = app.ApplicationServices.GetRequiredService();
- using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol))
+ using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false))
{
- var endpoint = $"{context.Connection.RemoteIpAddress}:{context.Connection.RemotePort}";
- await adapter.RunWebSocketConnectionAsync(webSocket, endpoint);
+ await adapter.RunWebSocketConnectionAsync(webSocket, context);
}
});
diff --git a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs
index e308ccd4..e8f570c2 100644
--- a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs
+++ b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs
@@ -3,8 +3,9 @@ using System.Net;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Client;
+using MQTTnet.Client.Options;
using MQTTnet.Diagnostics;
-using MQTTnet.Serializer;
+using MQTTnet.Formatter;
namespace MQTTnet.AspNetCore.Client
{
@@ -14,15 +15,16 @@ namespace MQTTnet.AspNetCore.Client
{
if (options == null) throw new ArgumentNullException(nameof(options));
- var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion };
-
switch (options.ChannelOptions)
{
case MqttClientTcpOptions tcpOptions:
{
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
var tcpConnection = new TcpConnection(endpoint);
- return new MqttConnectionContext(serializer, tcpConnection);
+
+ var writer = new SpanBasedMqttPacketWriter();
+ var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, writer);
+ return new MqttConnectionContext(formatter, tcpConnection);
}
default:
{
diff --git a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs
index 55192d6c..f5bb0b4c 100644
--- a/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs
+++ b/Source/MQTTnet.AspnetCore/Client/Tcp/SocketSender.cs
@@ -5,6 +5,10 @@ using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
+#if NETCOREAPP2_1
+using System.Runtime.InteropServices;
+#endif
+
namespace MQTTnet.AspNetCore.Client.Tcp
{
public class SocketSender
diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index dc477f76..86db7495 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -1,12 +1,13 @@
- netstandard2.0
+ netstandard2.0;netcoreapp2.1
7.2
+ true
@@ -15,6 +16,7 @@
+
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index a5e567a7..6febcba5 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Http.Connections.Features;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
using MQTTnet.Packets;
-using MQTTnet.Serializer;
using System;
using System.IO.Pipelines;
using System.Threading;
@@ -13,35 +14,68 @@ namespace MQTTnet.AspNetCore
{
public class MqttConnectionContext : IMqttChannelAdapter
{
- public MqttConnectionContext(
- IMqttPacketSerializer packetSerializer,
- ConnectionContext connection)
+ public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{
- PacketSerializer = packetSerializer;
- Connection = connection;
+ PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
+ Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+
+ if (Connection.Transport != null)
+ {
+ _input = Connection.Transport.Input;
+ _output = Connection.Transport.Output;
+ }
+
+ _reader = new SpanBasedMqttPacketBodyReader();
}
- public string Endpoint => Connection.ConnectionId;
+ private PipeReader _input;
+ private PipeWriter _output;
+ private readonly SpanBasedMqttPacketBodyReader _reader;
+
+ public string Endpoint
+ {
+ get
+ {
+ var connection = Http?.HttpContext?.Connection;
+ if (connection == null)
+ {
+ return Connection.ConnectionId;
+ }
+
+ return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
+ }
+ }
+
+ public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
+
+ private IHttpContextFeature Http => Connection.Features.Get();
+
public ConnectionContext Connection { get; }
- public IMqttPacketSerializer PacketSerializer { get; }
- public event EventHandler ReadingPacketStarted;
- public event EventHandler ReadingPacketCompleted;
+ public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
+
+ public long BytesSent { get; set; }
+ public long BytesReceived { get; set; }
+
+ public Action ReadingPacketStartedCallback { get; set; }
+ public Action ReadingPacketCompletedCallback { get; set; }
private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1);
- public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
+ public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
if (Connection is TcpConnection tcp && !tcp.IsConnected)
{
- return tcp.StartAsync();
+ await tcp.StartAsync().ConfigureAwait(false);
}
- return Task.CompletedTask;
+
+ _input = Connection.Transport.Input;
+ _output = Connection.Transport.Output;
}
public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
- Connection.Transport.Input.Complete();
- Connection.Transport.Output.Complete();
+ _input?.Complete();
+ _output?.Complete();
return Task.CompletedTask;
}
@@ -74,14 +108,15 @@ namespace MQTTnet.AspNetCore
{
if (!buffer.IsEmpty)
{
- if (PacketSerializer.TryDeserialize(buffer, out var packet, out consumed, out observed))
+ if (PacketFormatterAdapter.TryDecode(_reader, buffer, out var packet, out consumed, out observed, out var received))
{
+ BytesReceived += received;
return packet;
}
else
{
// we did receive something but the message is not yet complete
- ReadingPacketStarted?.Invoke(this, EventArgs.Empty);
+ ReadingPacketStartedCallback?.Invoke();
}
}
else if (readResult.IsCompleted)
@@ -100,22 +135,29 @@ namespace MQTTnet.AspNetCore
}
finally
{
- ReadingPacketCompleted?.Invoke(this, EventArgs.Empty);
+ ReadingPacketCompletedCallback?.Invoke();
}
cancellationToken.ThrowIfCancellationRequested();
return null;
}
- public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
+ public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
{
- var buffer = PacketSerializer.Serialize(packet).AsMemory();
- var output = Connection.Transport.Output;
+ var formatter = PacketFormatterAdapter;
+
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
- await output.WriteAsync(buffer, cancellationToken).ConfigureAwait(false);
+ var buffer = formatter.Encode(packet);
+ var msg = buffer.AsMemory();
+ var output = _output;
+ msg.CopyTo(output.GetMemory(msg.Length));
+ BytesSent += msg.Length;
+ PacketFormatterAdapter.FreeBuffer();
+ output.Advance(msg.Length);
+ await output.FlushAsync().ConfigureAwait(false);
}
finally
{
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs
index 23903894..8cc1022d 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs
@@ -1,16 +1,16 @@
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using MQTTnet.Adapter;
-using MQTTnet.Serializer;
using MQTTnet.Server;
using System;
using System.Threading.Tasks;
+using MQTTnet.Formatter;
namespace MQTTnet.AspNetCore
{
public class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapter
- {
- public event EventHandler ClientAccepted;
+ {
+ public Func ClientHandler { get; set; }
public override async Task OnConnectedAsync(ConnectionContext connection)
{
@@ -21,14 +21,15 @@ namespace MQTTnet.AspNetCore
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
}
-
- var serializer = new MqttPacketSerializer();
- using (var adapter = new MqttConnectionContext(serializer, connection))
+ var writer = new SpanBasedMqttPacketWriter();
+ var formatter = new MqttPacketFormatterAdapter(writer);
+ using (var adapter = new MqttConnectionContext(formatter, connection))
{
- var args = new MqttServerAdapterClientAcceptedEventArgs(adapter);
- ClientAccepted?.Invoke(this, args);
-
- await args.SessionTask;
+ var clientHandler = ClientHandler;
+ if (clientHandler != null)
+ {
+ await clientHandler(adapter).ConfigureAwait(false);
+ }
}
}
@@ -41,7 +42,7 @@ namespace MQTTnet.AspNetCore
{
return Task.CompletedTask;
}
-
+
public void Dispose()
{
}
diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
index dbd93604..455840cd 100644
--- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
+++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
@@ -1,17 +1,27 @@
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
+using MQTTnet.Formatter;
using MQTTnet.Implementations;
-using MQTTnet.Serializer;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
public class MqttWebSocketServerAdapter : IMqttServerAdapter
{
- public event EventHandler ClientAccepted;
+ private readonly IMqttNetChildLogger _logger;
+
+ public MqttWebSocketServerAdapter(IMqttNetChildLogger logger)
+ {
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ _logger = logger.CreateChildLogger(nameof(MqttTcpServerAdapter));
+ }
+
+ public Func ClientHandler { get; set; }
public Task StartAsync(IMqttServerOptions options)
{
@@ -23,24 +33,31 @@ 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 MqttPacketSerializer(), new MqttNetLogger().CreateChildLogger(nameof(MqttWebSocketServerAdapter)));
+ var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
+
+ var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
+ var isSecureConnection = clientCertificate != null;
+ clientCertificate?.Dispose();
- var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
- ClientAccepted?.Invoke(this, eventArgs);
-
- if (eventArgs.SessionTask != null)
+ var clientHandler = ClientHandler;
+ if (clientHandler != null)
{
- await eventArgs.SessionTask.ConfigureAwait(false);
+ var writer = new SpanBasedMqttPacketWriter();
+ var formatter = new MqttPacketFormatterAdapter(writer);
+ var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection);
+ using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger.CreateChildLogger(nameof(MqttWebSocketServerAdapter))))
+ {
+ await clientHandler(channelAdapter).ConfigureAwait(false);
+ }
}
}
-
+
public void Dispose()
{
- StopAsync().GetAwaiter().GetResult();
}
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs
index 6c770b9e..2746c7db 100644
--- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs
@@ -2,26 +2,36 @@
using System.Buffers;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
using MQTTnet.Packets;
-using MQTTnet.Serializer;
namespace MQTTnet.AspNetCore
{
public static class ReaderExtensions
{
- public static bool TryDeserialize(this IMqttPacketSerializer serializer, in ReadOnlySequence input, out MqttBasePacket packet, out SequencePosition consumed, out SequencePosition observed)
+ public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
+ SpanBasedMqttPacketBodyReader reader,
+ in ReadOnlySequence input,
+ out MqttBasePacket packet,
+ out SequencePosition consumed,
+ out SequencePosition observed,
+ out int bytesRead)
{
+ if (formatter == null) throw new ArgumentNullException(nameof(formatter));
+
packet = null;
consumed = input.Start;
observed = input.End;
+ bytesRead = 0;
var copy = input;
+
if (copy.Length < 2)
{
return false;
}
var fixedheader = copy.First.Span[0];
- if (!TryReadBodyLength(ref copy, out var bodyLength))
+ if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{
return false;
}
@@ -32,34 +42,45 @@ namespace MQTTnet.AspNetCore
}
var bodySlice = copy.Slice(0, bodyLength);
- var buffer = bodySlice.GetArray();
- packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length)));
+ var buffer = bodySlice.GetMemory();
+ reader.SetBuffer(buffer);
+
+ var receivedMqttPacket = new ReceivedMqttPacket(fixedheader, reader, buffer.Length + 2);
+
+ if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
+ {
+ formatter.DetectProtocolVersion(receivedMqttPacket);
+ }
+
+ packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
+ bytesRead = headerLength + bodyLength;
return true;
}
- private static byte[] GetArray(this in ReadOnlySequence input)
+ private static ReadOnlyMemory GetMemory(this in ReadOnlySequence input)
{
if (input.IsSingleSegment)
{
- return input.First.Span.ToArray();
+ return input.First;
}
// Should be rare
return input.ToArray();
}
- private static bool TryReadBodyLength(ref ReadOnlySequence input, out int result)
+ private static bool TryReadBodyLength(ref ReadOnlySequence input, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
var value = 0;
byte encodedByte;
var index = 1;
- result = 0;
-
- var temp = input.Slice(0, Math.Min(5, input.Length)).GetArray();
+ headerLength = 0;
+ bodyLength = 0;
+
+ var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory();
do
{
@@ -67,13 +88,13 @@ namespace MQTTnet.AspNetCore
{
return false;
}
- encodedByte = temp[index];
+ encodedByte = temp.Span[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
- throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.AsSpan(1, index).ToArray())}).");
+ throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
}
multiplier *= 128;
@@ -81,7 +102,8 @@ namespace MQTTnet.AspNetCore
input = input.Slice(index);
- result = value;
+ headerLength = index;
+ bodyLength = value;
return true;
}
}
diff --git a/Source/MQTTnet.AspnetCore/SpanBasedMqttPacketBodyReader.cs b/Source/MQTTnet.AspnetCore/SpanBasedMqttPacketBodyReader.cs
new file mode 100644
index 00000000..2b118f7b
--- /dev/null
+++ b/Source/MQTTnet.AspnetCore/SpanBasedMqttPacketBodyReader.cs
@@ -0,0 +1,124 @@
+using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
+using System;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace MQTTnet.AspNetCore
+{
+ public class SpanBasedMqttPacketBodyReader : IMqttPacketBodyReader
+ {
+ private ReadOnlyMemory _buffer;
+ private int _offset;
+
+ public void SetBuffer(ReadOnlyMemory buffer)
+ {
+ _buffer = buffer;
+ _offset = 0;
+ }
+
+ public ulong Length => (ulong)_buffer.Length;
+
+ public bool EndOfStream => _buffer.Length.Equals(_offset);
+
+ public ulong Offset => (ulong)_offset;
+
+ public byte ReadByte()
+ {
+ return _buffer.Span[_offset++];
+ }
+
+ public byte[] ReadRemainingData()
+ {
+ return _buffer.Slice(_offset).ToArray();
+ }
+
+ public byte[] ReadWithLengthPrefix()
+ {
+ return ReadSegmentWithLengthPrefix().ToArray();
+ }
+
+ private ReadOnlySpan ReadSegmentWithLengthPrefix()
+ {
+ var span = _buffer.Span;
+ var length = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(_offset));
+
+ var result = span.Slice(_offset+2, length);
+ _offset += 2 + length;
+ return result;
+ }
+
+ public unsafe string ReadStringWithLengthPrefix()
+ {
+ var buffer = ReadSegmentWithLengthPrefix();
+ if (buffer.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ fixed (byte* bytes = &buffer.GetPinnableReference())
+ {
+ var result = Encoding.UTF8.GetString(bytes, buffer.Length);
+ return result;
+ }
+ }
+
+ public ushort ReadTwoByteInteger()
+ {
+ var result = BinaryPrimitives.ReadUInt16BigEndian(_buffer.Span.Slice(_offset));
+ _offset += 2;
+ return result;
+ }
+
+ public uint ReadFourByteInteger()
+ {
+ var result = BinaryPrimitives.ReadUInt32BigEndian(_buffer.Span.Slice(_offset));
+ _offset += 4;
+ return result;
+ }
+
+ public uint ReadVariableLengthInteger()
+ {
+ var multiplier = 1;
+ var value = 0U;
+ byte encodedByte;
+
+ do
+ {
+ encodedByte = ReadByte();
+ value += (uint)((encodedByte & 127) * multiplier);
+
+ if (multiplier > 2097152)
+ {
+ throw new MqttProtocolViolationException("Variable length integer is invalid.");
+ }
+
+ multiplier *= 128;
+ } while ((encodedByte & 128) != 0);
+
+ return value;
+ }
+
+ public bool ReadBoolean()
+ {
+ var buffer = ReadByte();
+
+ if (buffer == 0)
+ {
+ return false;
+ }
+
+ if (buffer == 1)
+ {
+ return true;
+ }
+
+ throw new MqttProtocolViolationException("Boolean values can be 0 or 1 only.");
+ }
+
+ public void Seek(ulong position)
+ {
+ _offset = (int)position;
+ }
+ }
+}
diff --git a/Source/MQTTnet.AspnetCore/SpanBasedMqttPacketWriter.cs b/Source/MQTTnet.AspnetCore/SpanBasedMqttPacketWriter.cs
new file mode 100644
index 00000000..27e13c49
--- /dev/null
+++ b/Source/MQTTnet.AspnetCore/SpanBasedMqttPacketWriter.cs
@@ -0,0 +1,146 @@
+using MQTTnet.Formatter;
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Text;
+
+namespace MQTTnet.AspNetCore
+{
+ public class SpanBasedMqttPacketWriter : IMqttPacketWriter
+ {
+ private readonly ArrayPool _pool;
+
+ public SpanBasedMqttPacketWriter()
+ {
+ _pool = ArrayPool.Create();
+
+ }
+
+ private byte[] _buffer;
+ private int _position;
+
+ public int Length { get; set; }
+
+ public void FreeBuffer()
+ {
+ _pool.Return(_buffer);
+ }
+
+ public byte[] GetBuffer()
+ {
+ return _buffer;
+ }
+
+ public void Reset(int v)
+ {
+ _buffer = _pool.Rent(1500);
+ Length = v;
+ _position = v;
+ }
+
+ public void Seek(int v)
+ {
+ _position = v;
+ }
+
+ public void Write(byte value)
+ {
+ GrowIfNeeded(1);
+ _buffer[_position] = value;
+ Commit(1);
+ }
+
+ public void Write(ushort value)
+ {
+ GrowIfNeeded(2);
+
+ BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_position), value);
+ Commit(2);
+ }
+
+ public void Write(IMqttPacketWriter propertyWriter)
+ {
+ if (propertyWriter == null) throw new ArgumentNullException(nameof(propertyWriter));
+
+ GrowIfNeeded(propertyWriter.Length);
+ Write(propertyWriter.GetBuffer(), 0, propertyWriter.Length);
+ Commit(propertyWriter.Length);
+ }
+
+ public void Write(byte[] payload, int start, int length)
+ {
+ GrowIfNeeded(length);
+
+ payload.AsSpan(start, length).CopyTo(_buffer.AsSpan(_position));
+ Commit(length);
+ }
+
+ public void WriteVariableLengthInteger(uint value)
+ {
+ GrowIfNeeded(4);
+
+ var x = value;
+ do
+ {
+ var encodedByte = x % 128;
+ x = x / 128;
+ if (x > 0)
+ {
+ encodedByte = encodedByte | 128;
+ }
+
+ _buffer[_position] = (byte)encodedByte;
+ Commit(1);
+ } while (x > 0);
+ }
+
+ public void WriteWithLengthPrefix(string value)
+ {
+ var bytesLength = Encoding.UTF8.GetByteCount(value ?? string.Empty);
+ GrowIfNeeded(bytesLength + 2);
+
+ Write((ushort)bytesLength);
+ Encoding.UTF8.GetBytes(value ?? string.Empty, 0, value?.Length ?? 0, _buffer, _position);
+ Commit(bytesLength);
+ }
+
+ public void WriteWithLengthPrefix(byte[] payload)
+ {
+ GrowIfNeeded(payload.Length + 2);
+
+ Write((ushort)payload.Length);
+ payload.CopyTo(_buffer, _position);
+ Commit(payload.Length);
+ }
+
+ private void Commit(int count)
+ {
+ if (_position == Length)
+ {
+ Length += count;
+ }
+
+ _position += count;
+ }
+
+ private void GrowIfNeeded(int requiredAdditional)
+ {
+ var requiredTotal = _position + requiredAdditional;
+ if (_buffer.Length >= requiredTotal)
+ {
+ return;
+ }
+
+ var newBufferLength = _buffer.Length;
+ while (newBufferLength < requiredTotal)
+ {
+ newBufferLength *= 2;
+ }
+
+ var newBuffer = _pool.Rent(newBufferLength);
+ Array.Copy(_buffer, newBuffer, _buffer.Length);
+ _pool.Return(_buffer);
+ _buffer = newBuffer;
+ }
+ }
+}
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 b3835e3e..9919081f 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClient.cs
@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using MQTTnet.Client;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -12,14 +13,14 @@ namespace MQTTnet.Extensions.ManagedClient
int PendingApplicationMessagesCount { get; }
IManagedMqttClientOptions Options { get; }
- event EventHandler Connected;
- event EventHandler Disconnected;
+ IMqttClientConnectedHandler ConnectedHandler { get; set; }
+ IMqttClientDisconnectedHandler DisconnectedHandler { get; set; }
- event EventHandler ApplicationMessageProcessed;
- event EventHandler ApplicationMessageSkipped;
+ IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; }
+ IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
- event EventHandler ConnectingFailed;
- event EventHandler SynchronizingSubscriptionsFailed;
+ IConnectingFailedHandler ConnectingFailedHandler { get; set; }
+ ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync();
diff --git a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs
index f59aca7c..a705efea 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/IManagedMqttClientOptions.cs
@@ -1,5 +1,5 @@
using System;
-using MQTTnet.Client;
+using MQTTnet.Client.Options;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
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 866a75da..59f1c9a9 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
@@ -4,13 +4,15 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
+using MQTTnet.Client.Publishing;
+using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
-using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs;
-using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs;
namespace MQTTnet.Extensions.ManagedClient
{
@@ -25,21 +27,18 @@ namespace MQTTnet.Extensions.ManagedClient
private CancellationTokenSource _connectionCancellationToken;
private CancellationTokenSource _publishingCancellationToken;
+ private Task _maintainConnectionTask;
private ManagedMqttClientStorageManager _storageManager;
-
+
+ private bool _disposed = false;
private bool _subscriptionsNotPushed;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetChildLogger logger)
{
- if (logger == null) throw new ArgumentNullException(nameof(logger));
-
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
- _mqttClient.Connected += OnConnected;
- _mqttClient.Disconnected += OnDisconnected;
- _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
-
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateChildLogger(nameof(ManagedMqttClient));
}
@@ -48,18 +47,36 @@ namespace MQTTnet.Extensions.ManagedClient
public int PendingApplicationMessagesCount => _messageQueue.Count;
public IManagedMqttClientOptions Options { get; private set; }
- public event EventHandler Connected;
- public event EventHandler Disconnected;
+ public IMqttClientConnectedHandler ConnectedHandler
+ {
+ get => _mqttClient.ConnectedHandler;
+ set => _mqttClient.ConnectedHandler = value;
+ }
- public event EventHandler ApplicationMessageReceived;
- public event EventHandler ApplicationMessageProcessed;
- public event EventHandler ApplicationMessageSkipped;
+ public IMqttClientDisconnectedHandler DisconnectedHandler
+ {
+ get => _mqttClient.DisconnectedHandler;
+ set => _mqttClient.DisconnectedHandler = value;
+ }
- public event EventHandler ConnectingFailed;
- public event EventHandler SynchronizingSubscriptionsFailed;
+ public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler
+ {
+ get => _mqttClient.ApplicationMessageReceivedHandler;
+ set => _mqttClient.ApplicationMessageReceivedHandler = value;
+ }
+
+ public IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; }
+
+ public IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
+
+ public IConnectingFailedHandler ConnectingFailedHandler { get; set; }
+
+ public ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
public async Task StartAsync(IManagedMqttClientOptions options)
{
+ ThrowIfDisposed();
+
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
@@ -68,7 +85,7 @@ namespace MQTTnet.Extensions.ManagedClient
throw new NotSupportedException("The managed client does not support existing sessions.");
}
- if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started.");
+ if (!_maintainConnectionTask?.IsCompleted ?? false) throw new InvalidOperationException("The managed client is already started.");
Options = options;
@@ -86,56 +103,86 @@ namespace MQTTnet.Extensions.ManagedClient
_connectionCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
- Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
+ _maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
_logger.Info("Started");
}
- public Task StopAsync()
+ public async Task StopAsync()
{
+ ThrowIfDisposed();
+
StopPublishing();
StopMaintainingConnection();
_messageQueue.Clear();
- return Task.FromResult(0);
+ if (_maintainConnectionTask != null)
+ {
+ await Task.WhenAny(_maintainConnectionTask);
+ _maintainConnectionTask = null;
+ }
}
- public Task PublishAsync(MqttApplicationMessage applicationMessage)
+ public async Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
+ ThrowIfDisposed();
+
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
- return PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build());
+ await PublishAsync(new ManagedMqttApplicationMessageBuilder().WithApplicationMessage(applicationMessage).Build()).ConfigureAwait(false);
+ return new MqttClientPublishResult();
}
public async Task PublishAsync(ManagedMqttApplicationMessage applicationMessage)
{
+ ThrowIfDisposed();
+
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+ MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
+
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)
@@ -149,6 +196,8 @@ namespace MQTTnet.Extensions.ManagedClient
public Task SubscribeAsync(IEnumerable topicFilters)
{
+ ThrowIfDisposed();
+
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
lock (_subscriptions)
@@ -165,6 +214,8 @@ namespace MQTTnet.Extensions.ManagedClient
public Task UnsubscribeAsync(IEnumerable topics)
{
+ ThrowIfDisposed();
+
if (topics == null) throw new ArgumentNullException(nameof(topics));
lock (_subscriptions)
@@ -184,8 +235,31 @@ namespace MQTTnet.Extensions.ManagedClient
public void Dispose()
{
- _connectionCancellationToken?.Dispose();
- _publishingCancellationToken?.Dispose();
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ StopPublishing();
+ StopMaintainingConnection();
+
+ if (_maintainConnectionTask != null)
+ {
+ Task.WaitAny(_maintainConnectionTask);
+ _maintainConnectionTask = null;
+ }
+
+ _mqttClient.Dispose();
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(ManagedMqttClient));
+ }
}
private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
@@ -206,8 +280,19 @@ namespace MQTTnet.Extensions.ManagedClient
}
finally
{
- await _mqttClient.DisconnectAsync().ConfigureAwait(false);
- _logger.Info("Stopped");
+ if (!_disposed)
+ {
+ try
+ {
+ await _mqttClient.DisconnectAsync().ConfigureAwait(false);
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(exception, "Error while disconnecting.");
+ }
+
+ _logger.Info("Stopped");
+ }
}
}
@@ -332,20 +417,20 @@ namespace MQTTnet.Extensions.ManagedClient
}
finally
{
- ApplicationMessageProcessed?.Invoke(this, new ApplicationMessageProcessedEventArgs(message, transmitException));
+ ApplicationMessageProcessedHandler?.HandleApplicationMessageProcessedAsync(new ApplicationMessageProcessedEventArgs(message, transmitException)).GetAwaiter().GetResult();
}
}
private async Task SynchronizeSubscriptionsAsync()
{
- _logger.Info(nameof(ManagedMqttClient), "Synchronizing subscriptions");
+ _logger.Info("Synchronizing subscriptions");
List subscriptions;
HashSet unsubscriptions;
lock (_subscriptions)
{
- subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
+ subscriptions = _subscriptions.Select(i => new TopicFilter { Topic = i.Key, QualityOfServiceLevel = i.Value }).ToList();
unsubscriptions = new HashSet(_unsubscriptions);
_unsubscriptions.Clear();
@@ -362,12 +447,12 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (unsubscriptions.Any())
{
- await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false);
+ await _mqttClient.UnsubscribeAsync(unsubscriptions.ToArray()).ConfigureAwait(false);
}
if (subscriptions.Any())
{
- await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
+ await _mqttClient.SubscribeAsync(subscriptions.ToArray()).ConfigureAwait(false);
}
}
catch (Exception exception)
@@ -375,7 +460,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);
+ }
}
}
@@ -393,26 +482,16 @@ namespace MQTTnet.Extensions.ManagedClient
}
catch (Exception exception)
{
- ConnectingFailed?.Invoke(this, new MqttManagedProcessFailedEventArgs(exception));
+ var connectingFailedHandler = ConnectingFailedHandler;
+ if (connectingFailedHandler != null)
+ {
+ await connectingFailedHandler.HandleConnectingFailedAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
+ }
+
return ReconnectionResult.NotConnected;
}
}
-
- private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
- {
- ApplicationMessageReceived?.Invoke(this, eventArgs);
- }
-
- private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
- {
- Disconnected?.Invoke(this, eventArgs);
- }
-
- private void OnConnected(object sender, MqttClientConnectedEventArgs eventArgs)
- {
- Connected?.Invoke(this, eventArgs);
- }
-
+
private void StartPublishing()
{
if (_publishingCancellationToken != null)
@@ -440,4 +519,4 @@ namespace MQTTnet.Extensions.ManagedClient
_connectionCancellationToken = null;
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs
index ba5a16f7..b6387918 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientExtensions.cs
@@ -1,39 +1,229 @@
using System;
+using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
+using MQTTnet.Client.Publishing;
+using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.ManagedClient
{
public static class ManagedMqttClientExtensions
{
- public static Task SubscribeAsync(this IManagedMqttClient managedClient, params TopicFilter[] topicFilters)
+ public static IManagedMqttClient UseConnectedHandler(this IManagedMqttClient client, Func handler)
{
- if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+ if (client == null) throw new ArgumentNullException(nameof(client));
- return managedClient.SubscribeAsync(topicFilters);
+ if (handler == null)
+ {
+ return client.UseConnectedHandler((IMqttClientConnectedHandler)null);
+ }
+
+ return client.UseConnectedHandler(new MqttClientConnectedHandlerDelegate(handler));
}
- public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
+ public static IManagedMqttClient UseConnectedHandler(this IManagedMqttClient client, Action handler)
{
- if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseConnectedHandler((IMqttClientConnectedHandler)null);
+ }
+
+ return client.UseConnectedHandler(new MqttClientConnectedHandlerDelegate(handler));
+ }
+
+ public static IManagedMqttClient UseConnectedHandler(this IManagedMqttClient client, IMqttClientConnectedHandler handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ client.ConnectedHandler = handler;
+ return client;
+ }
+
+ public static IManagedMqttClient UseDisconnectedHandler(this IManagedMqttClient client, Func handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseDisconnectedHandler((IMqttClientDisconnectedHandler)null);
+ }
+
+ return client.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(handler));
+ }
+
+ public static IManagedMqttClient UseDisconnectedHandler(this IManagedMqttClient client, Action handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseDisconnectedHandler((IMqttClientDisconnectedHandler)null);
+ }
+
+ return client.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(handler));
+ }
+
+ public static IManagedMqttClient UseDisconnectedHandler(this IManagedMqttClient client, IMqttClientDisconnectedHandler handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ client.DisconnectedHandler = handler;
+ return client;
+ }
+
+ public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, Func handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
+ }
+
+ return client.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
+ }
+
+ public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, Action handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
+ }
+
+ return client.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
+ }
+
+ public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, IMqttApplicationMessageReceivedHandler handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ client.ApplicationMessageReceivedHandler = handler;
+ return client;
+ }
+
+ public static Task SubscribeAsync(this IManagedMqttClient client, params TopicFilter[] topicFilters)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ return client.SubscribeAsync(topicFilters);
+ }
+
+ public static Task SubscribeAsync(this IManagedMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
- return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
+ return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}
- public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic)
+ public static Task SubscribeAsync(this IManagedMqttClient client, string topic)
{
- if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+ if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
- return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
+ return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
}
- public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters)
+ public static Task UnsubscribeAsync(this IManagedMqttClient client, params string[] topicFilters)
{
- if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+ if (client == null) throw new ArgumentNullException(nameof(client));
- return managedClient.UnsubscribeAsync(topicFilters);
+ return client.UnsubscribeAsync(topicFilters);
+ }
+
+ public static async Task PublishAsync(this IManagedMqttClient client, IEnumerable applicationMessages)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
+
+ foreach (var applicationMessage in applicationMessages)
+ {
+ await client.PublishAsync(applicationMessage).ConfigureAwait(false);
+ }
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, MqttApplicationMessage applicationMessage)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
+ return client.PublishAsync(applicationMessage, CancellationToken.None);
+ }
+
+ public static async Task PublishAsync(this IManagedMqttClient client, params MqttApplicationMessage[] applicationMessages)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
+
+ foreach (var applicationMessage in applicationMessages)
+ {
+ await client.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, string topic)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(builder => builder
+ .WithTopic(topic));
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, string topic, string payload)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(builder => builder
+ .WithTopic(topic)
+ .WithPayload(payload));
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(builder => builder
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .WithQualityOfServiceLevel(qualityOfServiceLevel));
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(builder => builder
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .WithQualityOfServiceLevel(qualityOfServiceLevel)
+ .WithRetainFlag(retain));
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, Func builder, CancellationToken cancellationToken)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ var message = builder(new MqttApplicationMessageBuilder()).Build();
+ return client.PublishAsync(message, cancellationToken);
+ }
+
+ public static Task PublishAsync(this IManagedMqttClient client, Func builder)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ var message = builder(new MqttApplicationMessageBuilder()).Build();
+ return client.PublishAsync(message, CancellationToken.None);
}
}
}
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs
index cba801bb..537e1bb9 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptions.cs
@@ -1,5 +1,6 @@
using System;
using MQTTnet.Client;
+using MQTTnet.Client.Options;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs
index 3ea5b050..f58453ce 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientOptionsBuilder.cs
@@ -1,5 +1,6 @@
using System;
using MQTTnet.Client;
+using MQTTnet.Client.Options;
using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs
index 821a7d92..772f102e 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClientStorageManager.cs
@@ -30,7 +30,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
- using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
+ using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
_messages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false);
@@ -41,7 +41,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
- using (await _messagesLock.LockAsync(CancellationToken.None).ConfigureAwait(false))
+ using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
var index = _messages.IndexOf(applicationMessage);
if (index == -1)
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedProcessFailedEventArgs.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedProcessFailedEventArgs.cs
new file mode 100644
index 00000000..bb0cf683
--- /dev/null
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedProcessFailedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace MQTTnet.Extensions.ManagedClient
+{
+ public class ManagedProcessFailedEventArgs : EventArgs
+ {
+ public ManagedProcessFailedEventArgs(Exception exception)
+ {
+ Exception = exception ?? throw new ArgumentNullException(nameof(exception));
+ }
+
+ public Exception Exception { get; }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs b/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs
index d2ad020d..37393ac2 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/MqttFactoryExtensions.cs
@@ -1,24 +1,23 @@
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));
- return new ManagedMqttClient(factory.CreateMqttClient(), logger.CreateChildLogger());
+ return new ManagedMqttClient(factory.CreateMqttClient(logger), logger.CreateChildLogger());
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs b/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs
deleted file mode 100644
index 68dc1608..00000000
--- a/Source/MQTTnet.Extensions.ManagedClient/MqttManagedProcessFailedEventArgs.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace MQTTnet.Extensions.ManagedClient
-{
- public class MqttManagedProcessFailedEventArgs : EventArgs
- {
- public MqttManagedProcessFailedEventArgs(Exception exception)
- {
- Exception = exception;
- }
-
- public Exception Exception { get; }
- }
-}
diff --git a/Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs b/Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs
new file mode 100644
index 00000000..72d6e4a0
--- /dev/null
+++ b/Source/MQTTnet.Extensions.ManagedClient/SynchronizingSubscriptionsFailedHandlerDelegate.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Extensions.ManagedClient
+{
+ public class SynchronizingSubscriptionsFailedHandlerDelegate : ISynchronizingSubscriptionsFailedHandler
+ {
+ private readonly Func _handler;
+
+ public SynchronizingSubscriptionsFailedHandlerDelegate(Action handler)
+ {
+ if (handler == null) throw new ArgumentNullException(nameof(handler));
+
+ _handler = context =>
+ {
+ handler(context);
+ return Task.FromResult(0);
+ };
+ }
+
+ public SynchronizingSubscriptionsFailedHandlerDelegate(Func handler)
+ {
+ _handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ }
+
+ public Task HandleSynchronizingSubscriptionsFailedAsync(ManagedProcessFailedEventArgs eventArgs)
+ {
+ return _handler(eventArgs);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
index ebf66ec1..c025bdfa 100644
--- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
+++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
@@ -13,12 +13,17 @@ namespace MQTTnet.Extensions.Rpc
{
private readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>();
private readonly IMqttClient _mqttClient;
+ private readonly RpcAwareApplicationMessageReceivedHandler _applicationMessageReceivedHandler;
public MqttRpcClient(IMqttClient mqttClient)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
- _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
+ _applicationMessageReceivedHandler = new RpcAwareApplicationMessageReceivedHandler(
+ mqttClient.ApplicationMessageReceivedHandler,
+ HandleApplicationMessageReceivedAsync);
+
+ _mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler;
}
public Task ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
@@ -45,6 +50,11 @@ namespace MQTTnet.Extensions.Rpc
throw new ArgumentException("The method name cannot contain /, + or #.");
}
+ if (!(_mqttClient.ApplicationMessageReceivedHandler is RpcAwareApplicationMessageReceivedHandler))
+ {
+ throw new InvalidOperationException("The application message received handler was modified.");
+ }
+
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
var responseTopic = requestTopic + "/response";
@@ -102,26 +112,25 @@ namespace MQTTnet.Extensions.Rpc
}
}
- private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
+ private Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs))
{
- return;
- }
-
- if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
- {
- return;
+ return Task.FromResult(0);
}
tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
+
+ return Task.FromResult(0);
}
public void Dispose()
{
+ _mqttClient.ApplicationMessageReceivedHandler = _applicationMessageReceivedHandler.OriginalHandler;
+
foreach (var tcs in _waitingCalls)
{
- tcs.Value.SetCanceled();
+ tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
diff --git a/Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs b/Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs
new file mode 100644
index 00000000..9d90fb19
--- /dev/null
+++ b/Source/MQTTnet.Extensions.Rpc/RpcAwareApplicationMessageReceivedHandler.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+using MQTTnet.Client.Receiving;
+
+namespace MQTTnet.Extensions.Rpc
+{
+ public class RpcAwareApplicationMessageReceivedHandler : IMqttApplicationMessageReceivedHandler
+ {
+ private readonly Func _handleReceivedApplicationMessageAsync;
+
+ public RpcAwareApplicationMessageReceivedHandler(
+ IMqttApplicationMessageReceivedHandler originalHandler,
+ Func handleReceivedApplicationMessageAsync)
+ {
+ OriginalHandler = originalHandler;
+ _handleReceivedApplicationMessageAsync = handleReceivedApplicationMessageAsync ?? throw new ArgumentNullException(nameof(handleReceivedApplicationMessageAsync));
+ }
+
+ public IMqttApplicationMessageReceivedHandler OriginalHandler { get; }
+
+ public async Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
+ {
+ if (OriginalHandler != null)
+ {
+ await OriginalHandler.HandleApplicationMessageReceivedAsync(eventArgs).ConfigureAwait(false);
+ }
+
+ await _handleReceivedApplicationMessageAsync(eventArgs).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Configuration/RetainedApplicationMessagesModel.cs b/Source/MQTTnet.Server/Configuration/RetainedApplicationMessagesModel.cs
new file mode 100644
index 00000000..7abae83f
--- /dev/null
+++ b/Source/MQTTnet.Server/Configuration/RetainedApplicationMessagesModel.cs
@@ -0,0 +1,11 @@
+namespace MQTTnet.Server.Configuration
+{
+ public class RetainedApplicationMessagesModel
+ {
+ public bool Persist { get; set; } = false;
+
+ public int WriteInterval { get; set; } = 10;
+
+ public string Filename { get; set; } = "RetainedApplicationMessages.json";
+ }
+}
diff --git a/Source/MQTTnet.Server/Configuration/SettingsModel.cs b/Source/MQTTnet.Server/Configuration/SettingsModel.cs
new file mode 100644
index 00000000..ce5336d2
--- /dev/null
+++ b/Source/MQTTnet.Server/Configuration/SettingsModel.cs
@@ -0,0 +1,53 @@
+namespace MQTTnet.Server.Configuration
+{
+ ///
+ /// Main Settings Model
+ ///
+ public class 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 TcpEndPointModel TcpEndPoint { get; set; } = new TcpEndPointModel();
+
+ ///
+ /// Encryption Listen Settings
+ ///
+ public TcpEndPointModel EncryptedTcpEndPoint { get; set; } = new TcpEndPointModel();
+
+ ///
+ /// Settings for the Web Socket endpoint.
+ ///
+ public WebSocketEndPointModel WebSocketEndPoint { get; set; } = new WebSocketEndPointModel();
+
+ ///
+ /// Set limit for max pending messages per client
+ ///
+ public int MaxPendingMessagesPerClient { get; set; } = 250;
+
+ ///
+ /// The settings for retained messages.
+ ///
+ public RetainedApplicationMessagesModel RetainedApplicationMessages { get; set; } = new RetainedApplicationMessagesModel();
+
+ ///
+ /// Enables or disables the MQTTnet internal logging.
+ ///
+ public bool EnableDebugLogging { get; set; } = false;
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs b/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs
new file mode 100644
index 00000000..82213901
--- /dev/null
+++ b/Source/MQTTnet.Server/Configuration/TcpEndpointModel.cs
@@ -0,0 +1,121 @@
+using System.IO;
+using System.Net;
+
+namespace MQTTnet.Server.Configuration
+{
+ ///
+ /// Listen Entry Settings Model
+ ///
+ public class TcpEndPointModel
+ {
+ ///
+ /// Path to Certificate
+ ///
+ public string CertificatePath { get; set; }
+
+ ///
+ /// Enabled / Disable
+ ///
+ public bool Enabled { get; set; } = true;
+
+ ///
+ /// Listen Address
+ ///
+ public string IPv4 { get; set; }
+
+ ///
+ /// Listen Address
+ ///
+ public string IPv6 { get; set; }
+
+ ///
+ /// Listen Port
+ ///
+ public int Port { get; set; } = 1883;
+
+ ///
+ /// Read Certificate file
+ ///
+ ///
+ public byte[] ReadCertificate()
+ {
+ if (string.IsNullOrEmpty(CertificatePath) || string.IsNullOrWhiteSpace(CertificatePath))
+ {
+ throw new FileNotFoundException("No path set");
+ }
+
+ if (!File.Exists(CertificatePath))
+ {
+ throw new FileNotFoundException($"Could not find Certificate in path: {CertificatePath}");
+ }
+
+ return File.ReadAllBytes(CertificatePath);
+ }
+
+ ///
+ /// Read IPv4
+ ///
+ ///
+ public bool TryReadIPv4(out IPAddress address)
+ {
+ if (IPv4 == "*")
+ {
+ address = IPAddress.Any;
+ return true;
+ }
+
+ if (IPv4 == "localhost")
+ {
+ address = IPAddress.Loopback;
+ return true;
+ }
+
+ if (IPv4 == "disable")
+ {
+ address = IPAddress.None;
+ return true;
+ }
+
+ if (IPAddress.TryParse(IPv4, out var ip))
+ {
+ address = ip;
+ return true;
+ }
+
+ throw new System.Exception($"Could not parse IPv4 address: {IPv4}");
+ }
+
+ ///
+ /// Read IPv6
+ ///
+ ///
+ public bool TryReadIPv6(out IPAddress address)
+ {
+ if (IPv6 == "*")
+ {
+ address = IPAddress.IPv6Any;
+ return true;
+ }
+
+ if (IPv6 == "localhost")
+ {
+ address = IPAddress.IPv6Loopback;
+ return true;
+ }
+
+ if (IPv6 == "disable")
+ {
+ address = IPAddress.None;
+ return true;
+ }
+
+ if (IPAddress.TryParse(IPv6, out var ip))
+ {
+ address = ip;
+ return true;
+ }
+
+ throw new System.Exception($"Could not parse IPv6 address: {IPv6}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/Configuration/WebSocketEndpointModel.cs b/Source/MQTTnet.Server/Configuration/WebSocketEndpointModel.cs
new file mode 100644
index 00000000..50a1dae0
--- /dev/null
+++ b/Source/MQTTnet.Server/Configuration/WebSocketEndpointModel.cs
@@ -0,0 +1,18 @@
+
+using System.Collections.Generic;
+
+namespace MQTTnet.Server.Configuration
+{
+ public class WebSocketEndPointModel
+ {
+ public bool Enabled { get; set; } = true;
+
+ public string Path { get; set; } = "/mqtt";
+
+ public int ReceiveBufferSize { get; set; } = 4096;
+
+ public int KeepAliveInterval { get; set; } = 120;
+
+ public List AllowedOrigins { get; set; }
+ }
+}
diff --git a/Source/MQTTnet.Server/Controllers/ClientsController.cs b/Source/MQTTnet.Server/Controllers/ClientsController.cs
new file mode 100644
index 00000000..afc75119
--- /dev/null
+++ b/Source/MQTTnet.Server/Controllers/ClientsController.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using System.Web;
+using Microsoft.AspNetCore.Mvc;
+using MQTTnet.Server.Mqtt;
+using MQTTnet.Server.Status;
+
+namespace MQTTnet.Server.Controllers
+{
+ [ApiController]
+ public class ClientsController : ControllerBase
+ {
+ private readonly MqttServerService _mqttServerService;
+
+ public ClientsController(MqttServerService mqttServerService)
+ {
+ _mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService));
+ }
+
+ [Route("api/v1/clients")]
+ [HttpGet]
+ public async Task>> GetClients()
+ {
+ return new ObjectResult(await _mqttServerService.GetClientStatusAsync());
+ }
+
+ [Route("api/v1/clients/{clientId}")]
+ [HttpGet]
+ public async Task> GetClient(string clientId)
+ {
+ clientId = HttpUtility.UrlDecode(clientId);
+
+ var client = (await _mqttServerService.GetClientStatusAsync()).FirstOrDefault(c => c.ClientId == clientId);
+ if (client == null)
+ {
+ return new StatusCodeResult((int)HttpStatusCode.NotFound);
+ }
+
+ return new ObjectResult(client);
+ }
+
+ [Route("api/v1/clients/{clientId}")]
+ [HttpDelete]
+ public async Task DeleteClient(string clientId)
+ {
+ clientId = HttpUtility.UrlDecode(clientId);
+
+ var client = (await _mqttServerService.GetClientStatusAsync()).FirstOrDefault(c => c.ClientId == clientId);
+ if (client == null)
+ {
+ return new StatusCodeResult((int)HttpStatusCode.NotFound);
+ }
+
+ await client.DisconnectAsync();
+ return StatusCode((int)HttpStatusCode.NoContent);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Controllers/RetainedApplicationMessagesController.cs b/Source/MQTTnet.Server/Controllers/RetainedApplicationMessagesController.cs
new file mode 100644
index 00000000..30110b38
--- /dev/null
+++ b/Source/MQTTnet.Server/Controllers/RetainedApplicationMessagesController.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using System.Web;
+using Microsoft.AspNetCore.Mvc;
+using MQTTnet.Server.Mqtt;
+
+namespace MQTTnet.Server.Controllers
+{
+ [ApiController]
+ public class RetainedApplicationMessagesController : ControllerBase
+ {
+ private readonly MqttServerService _mqttServerService;
+
+ public RetainedApplicationMessagesController(MqttServerService mqttServerService)
+ {
+ _mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService));
+ }
+
+ [Route("api/v1/retainedApplicationMessages")]
+ [HttpGet]
+ public async Task>> GetRetainedApplicationMessages()
+ {
+ return new ObjectResult(await _mqttServerService.GetRetainedApplicationMessagesAsync());
+ }
+
+ [Route("api/v1/retainedApplicationMessages/{topic}")]
+ [HttpGet]
+ public async Task> GetRetainedApplicationMessage(string topic)
+ {
+ topic = HttpUtility.UrlDecode(topic);
+
+ var applicationMessage = (await _mqttServerService.GetRetainedApplicationMessagesAsync()).FirstOrDefault(c => c.Topic == topic);
+ if (applicationMessage == null)
+ {
+ return new StatusCodeResult((int)HttpStatusCode.NotFound);
+ }
+
+ return new ObjectResult(applicationMessage);
+ }
+
+ [Route("api/v1/retainedApplicationMessages")]
+ [HttpDelete]
+ public async Task DeleteRetainedApplicationMessages()
+ {
+ await _mqttServerService.ClearRetainedApplicationMessagesAsync();
+ return StatusCode((int)HttpStatusCode.NoContent);
+ }
+
+ [Route("api/v1/retainedApplicationMessages/{topic}")]
+ [HttpDelete]
+ public async Task DeleteRetainedApplicationMessage(string topic)
+ {
+ topic = HttpUtility.UrlDecode(topic);
+
+ await _mqttServerService.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithRetainFlag().Build());
+ return StatusCode((int)HttpStatusCode.NoContent);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Controllers/ScriptsController.cs b/Source/MQTTnet.Server/Controllers/ScriptsController.cs
new file mode 100644
index 00000000..1b2a7032
--- /dev/null
+++ b/Source/MQTTnet.Server/Controllers/ScriptsController.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Server.Controllers
+{
+ public class ScriptsController
+ {
+ }
+}
diff --git a/Source/MQTTnet.Server/Controllers/SessionsController.cs b/Source/MQTTnet.Server/Controllers/SessionsController.cs
new file mode 100644
index 00000000..415d4b9c
--- /dev/null
+++ b/Source/MQTTnet.Server/Controllers/SessionsController.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using System.Web;
+using Microsoft.AspNetCore.Mvc;
+using MQTTnet.Server.Mqtt;
+using MQTTnet.Server.Status;
+
+namespace MQTTnet.Server.Controllers
+{
+ [ApiController]
+ public class SessionsController : ControllerBase
+ {
+ private readonly MqttServerService _mqttServerService;
+
+ public SessionsController(MqttServerService mqttServerService)
+ {
+ _mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService));
+ }
+
+ [Route("api/v1/sessions")]
+ [HttpGet]
+ public async Task>> GetSessions()
+ {
+ return new ObjectResult(await _mqttServerService.GetSessionStatusAsync());
+ }
+
+ [Route("api/v1/sessions/{clientId}")]
+ [HttpGet]
+ public async Task> GetSession(string clientId)
+ {
+ clientId = HttpUtility.UrlDecode(clientId);
+
+ var session = (await _mqttServerService.GetSessionStatusAsync()).FirstOrDefault(c => c.ClientId == clientId);
+ if (session == null)
+ {
+ return new StatusCodeResult((int)HttpStatusCode.NotFound);
+ }
+
+ return new ObjectResult(session);
+ }
+
+ [Route("api/v1/sessions/{clientId}")]
+ [HttpDelete]
+ public async Task DeleteSession(string clientId)
+ {
+ clientId = HttpUtility.UrlDecode(clientId);
+
+ var session = (await _mqttServerService.GetSessionStatusAsync()).FirstOrDefault(c => c.ClientId == clientId);
+ if (session == null)
+ {
+ return new StatusCodeResult((int)HttpStatusCode.NotFound);
+ }
+
+ await session.DeleteAsync();
+ return StatusCode((int)HttpStatusCode.NoContent);
+ }
+
+ [Route("api/v1/sessions/{clientId}/pendingApplicationMessages")]
+ [HttpDelete]
+ public async Task DeletePendingApplicationMessages(string clientId)
+ {
+ clientId = HttpUtility.UrlDecode(clientId);
+
+ var session = (await _mqttServerService.GetSessionStatusAsync()).FirstOrDefault(c => c.ClientId == clientId);
+ if (session == null)
+ {
+ return new StatusCodeResult((int)HttpStatusCode.NotFound);
+ }
+
+ await session.ClearPendingApplicationMessagesAsync();
+ return StatusCode((int)HttpStatusCode.NoContent);
+ }
+ }
+}
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..3f521092
--- /dev/null
+++ b/Source/MQTTnet.Server/Logging/MqttNetLoggerWrapper.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Threading;
+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);
+ _logger.Log(convertedLogLevel, exception, message, parameters);
+
+ var logMessagePublishedEvent = LogMessagePublished;
+ if (logMessagePublishedEvent != null)
+ {
+ var logMessage = new MqttNetLogMessage(null, DateTime.UtcNow, Thread.CurrentThread.ManagedThreadId, source, logLevel, message, exception);
+ logMessagePublishedEvent.Invoke(this, new MqttNetLogMessagePublishedEventArgs(logMessage));
+ }
+ }
+
+ 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.Debug;
+ }
+
+ return LogLevel.Debug;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj
new file mode 100644
index 00000000..c322548c
--- /dev/null
+++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj
@@ -0,0 +1,74 @@
+
+
+
+ netcoreapp2.2
+ InProcess
+ MQTTnet.Server
+ MQTTnet.Server
+ False
+
+ MQTTnet
+
+
+
+ false
+ false
+ latest
+ 3.3
+
+
+
+
+
+
+
+
+ NU1605
+ 1701;1702
+
+
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+
+
+
diff --git a/Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs b/Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs
new file mode 100644
index 00000000..06bf5467
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/CustomMqttFactory.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Adapter;
+using MQTTnet.Diagnostics;
+using MQTTnet.Server.Configuration;
+using MQTTnet.Server.Logging;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class CustomMqttFactory
+ {
+ private readonly MqttFactory _mqttFactory;
+
+ public CustomMqttFactory(SettingsModel settings, ILogger logger)
+ {
+ if (settings == null) throw new ArgumentNullException(nameof(settings));
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ // It is important to avoid injecting the logger wrapper to ensure that no
+ // unused log messages are generated by the MQTTnet library. Debug logging
+ // has a huge performance impact.
+ if (settings.EnableDebugLogging)
+ {
+ var mqttNetLogger = new MqttNetLoggerWrapper(logger);
+ _mqttFactory = new MqttFactory(mqttNetLogger);
+
+ logger.LogWarning("Debug logging is enabled. Performance of MQTTnet Server is decreased!");
+ }
+ else
+ {
+ _mqttFactory = new MqttFactory();
+ }
+
+ Logger = _mqttFactory.DefaultLogger;
+
+ }
+
+ public IMqttNetLogger Logger { get; }
+
+ public IMqttServer CreateMqttServer(List adapters)
+ {
+ if (adapters == null) throw new ArgumentNullException(nameof(adapters));
+
+ return _mqttFactory.CreateMqttServer(adapters);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttApplicationMessageInterceptor.cs
new file mode 100644
index 00000000..c5f3afd4
--- /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/MqttClientConnectedHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientConnectedHandler.cs
new file mode 100644
index 00000000..8d3879e7
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttClientConnectedHandler.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Server.Scripting;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttClientConnectedHandler : IMqttServerClientConnectedHandler
+ {
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public MqttClientConnectedHandler(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs)
+ {
+ try
+ {
+ var pythonEventArgs = new PythonDictionary
+ {
+ { "client_id", eventArgs.ClientId }
+ };
+
+ _pythonScriptHostService.InvokeOptionalFunction("on_client_connected", pythonEventArgs);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while handling client connected event.");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs
new file mode 100644
index 00000000..f7b364e8
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttClientDisconnectedHandler.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Server.Scripting;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttClientDisconnectedHandler : IMqttServerClientDisconnectedHandler
+ {
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public MqttClientDisconnectedHandler(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs)
+ {
+ try
+ {
+ var pythonEventArgs = new PythonDictionary
+ {
+ { "client_id", eventArgs.ClientId },
+ { "type", PythonConvert.Pythonfy(eventArgs.DisconnectType) }
+ };
+
+ _pythonScriptHostService.InvokeOptionalFunction("on_client_disconnected", pythonEventArgs);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while handling client disconnected event.");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs
new file mode 100644
index 00000000..6d90b448
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttClientSubscribedTopicHandler.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Server.Scripting;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttClientSubscribedTopicHandler : IMqttServerClientSubscribedTopicHandler
+ {
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public MqttClientSubscribedTopicHandler(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs)
+ {
+ try
+ {
+ var pythonEventArgs = new PythonDictionary
+ {
+ { "client_id", eventArgs.ClientId },
+ { "topic", eventArgs.TopicFilter.Topic },
+ { "qos", (int)eventArgs.TopicFilter.QualityOfServiceLevel }
+ };
+
+ _pythonScriptHostService.InvokeOptionalFunction("on_client_subscribed_topic", pythonEventArgs);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while handling client subscribed topic event.");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs b/Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs
new file mode 100644
index 00000000..ac42f9ee
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttClientUnsubscribedTopicHandler.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Server.Scripting;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttClientUnsubscribedTopicHandler : IMqttServerClientUnsubscribedTopicHandler
+ {
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public MqttClientUnsubscribedTopicHandler(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs)
+ {
+ try
+ {
+ var pythonEventArgs = new PythonDictionary
+ {
+ { "client_id", eventArgs.ClientId },
+ { "topic", eventArgs.TopicFilter }
+ };
+
+ _pythonScriptHostService.InvokeOptionalFunction("on_client_unsubscribed_topic", pythonEventArgs);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while handling client unsubscribed topic event.");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs
new file mode 100644
index 00000000..83f66039
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttConnectionValidator.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Protocol;
+using MQTTnet.Server.Scripting;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttConnectionValidator : IMqttServerConnectionValidator
+ {
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public MqttConnectionValidator(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public Task ValidateConnectionAsync(MqttConnectionValidatorContext context)
+ {
+ try
+ {
+ var pythonContext = new PythonDictionary
+ {
+ { "client_id", context.ClientId },
+ { "endpoint", context.Endpoint },
+ { "is_secure_connection", context.IsSecureConnection },
+ { "username", context.Username },
+ { "password", context.Password },
+ { "result", PythonConvert.Pythonfy(context.ReturnCode) }
+ };
+
+ _pythonScriptHostService.InvokeOptionalFunction("on_validate_client_connection", pythonContext);
+
+ context.ReturnCode = PythonConvert.ParseEnum((string)pythonContext["result"]);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while validating client connection.");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs
new file mode 100644
index 00000000..42ec60b8
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs
@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.WebSockets;
+using System.Security.Authentication;
+using System.Text;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Adapter;
+using MQTTnet.AspNetCore;
+using MQTTnet.Client.Publishing;
+using MQTTnet.Implementations;
+using MQTTnet.Protocol;
+using MQTTnet.Server.Configuration;
+using MQTTnet.Server.Scripting;
+using MQTTnet.Server.Status;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttServerService
+ {
+ private readonly ILogger _logger;
+
+ private readonly SettingsModel _settings;
+ private readonly MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor;
+ private readonly MqttServerStorage _mqttServerStorage;
+ private readonly MqttClientConnectedHandler _mqttClientConnectedHandler;
+ private readonly MqttClientDisconnectedHandler _mqttClientDisconnectedHandler;
+ private readonly MqttClientSubscribedTopicHandler _mqttClientSubscribedTopicHandler;
+ private readonly MqttClientUnsubscribedTopicHandler _mqttClientUnsubscribedTopicHandler;
+ private readonly MqttConnectionValidator _mqttConnectionValidator;
+ private readonly IMqttServer _mqttServer;
+ private readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor;
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly MqttWebSocketServerAdapter _webSocketServerAdapter;
+
+ public MqttServerService(
+ SettingsModel settings,
+ CustomMqttFactory mqttFactory,
+ MqttClientConnectedHandler mqttClientConnectedHandler,
+ MqttClientDisconnectedHandler mqttClientDisconnectedHandler,
+ MqttClientSubscribedTopicHandler mqttClientSubscribedTopicHandler,
+ MqttClientUnsubscribedTopicHandler mqttClientUnsubscribedTopicHandler,
+ MqttConnectionValidator mqttConnectionValidator,
+ MqttSubscriptionInterceptor mqttSubscriptionInterceptor,
+ MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor,
+ MqttServerStorage mqttServerStorage,
+ PythonScriptHostService pythonScriptHostService,
+ ILogger logger)
+ {
+ _settings = settings ?? throw new ArgumentNullException(nameof(settings));
+ _mqttClientConnectedHandler = mqttClientConnectedHandler ?? throw new ArgumentNullException(nameof(mqttClientConnectedHandler));
+ _mqttClientDisconnectedHandler = mqttClientDisconnectedHandler ?? throw new ArgumentNullException(nameof(mqttClientDisconnectedHandler));
+ _mqttClientSubscribedTopicHandler = mqttClientSubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientSubscribedTopicHandler));
+ _mqttClientUnsubscribedTopicHandler = mqttClientUnsubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientUnsubscribedTopicHandler));
+ _mqttConnectionValidator = mqttConnectionValidator ?? throw new ArgumentNullException(nameof(mqttConnectionValidator));
+ _mqttSubscriptionInterceptor = mqttSubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttSubscriptionInterceptor));
+ _mqttApplicationMessageInterceptor = mqttApplicationMessageInterceptor ?? throw new ArgumentNullException(nameof(mqttApplicationMessageInterceptor));
+ _mqttServerStorage = mqttServerStorage ?? throw new ArgumentNullException(nameof(mqttServerStorage));
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+
+ _webSocketServerAdapter = new MqttWebSocketServerAdapter(mqttFactory.Logger.CreateChildLogger());
+
+ var adapters = new List
+ {
+ new MqttTcpServerAdapter(mqttFactory.Logger.CreateChildLogger()),
+ _webSocketServerAdapter
+ };
+
+ _mqttServer = mqttFactory.CreateMqttServer(adapters);
+ }
+
+ public void Configure()
+ {
+ _pythonScriptHostService.RegisterProxyObject("publish", new Action(Publish));
+
+ _mqttServerStorage.Configure();
+
+ _mqttServer.ClientConnectedHandler = _mqttClientConnectedHandler;
+ _mqttServer.ClientDisconnectedHandler = _mqttClientDisconnectedHandler;
+ _mqttServer.ClientSubscribedTopicHandler = _mqttClientSubscribedTopicHandler;
+ _mqttServer.ClientUnsubscribedTopicHandler = _mqttClientUnsubscribedTopicHandler;
+
+ _mqttServer.StartAsync(CreateMqttServerOptions()).GetAwaiter().GetResult();
+
+ _logger.LogInformation("MQTT server started.");
+ }
+
+ public Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
+ {
+ return _webSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, httpContext);
+ }
+
+ public Task> GetClientStatusAsync()
+ {
+ return _mqttServer.GetClientStatusAsync();
+ }
+
+ public Task> GetSessionStatusAsync()
+ {
+ return _mqttServer.GetSessionStatusAsync();
+ }
+
+ public Task ClearRetainedApplicationMessagesAsync()
+ {
+ return _mqttServer.ClearRetainedApplicationMessagesAsync();
+ }
+
+ public Task> GetRetainedApplicationMessagesAsync()
+ {
+ return _mqttServer.GetRetainedApplicationMessagesAsync();
+ }
+
+ public Task PublishAsync(MqttApplicationMessage applicationMessage)
+ {
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
+ return _mqttServer.PublishAsync(applicationMessage);
+ }
+
+ private void Publish(PythonDictionary parameters)
+ {
+ try
+ {
+ var applicationMessageBuilder = new MqttApplicationMessageBuilder()
+ .WithTopic((string)parameters.get("topic", null))
+ .WithRetainFlag((bool)parameters.get("retain", false))
+ .WithQualityOfServiceLevel((MqttQualityOfServiceLevel)(int)parameters.get("qos", 0));
+
+ var payload = parameters.get("payload", null);
+ byte[] binaryPayload;
+
+ if (payload == null)
+ {
+ binaryPayload = new byte[0];
+ }
+ else if (payload is string stringPayload)
+ {
+ binaryPayload = Encoding.UTF8.GetBytes(stringPayload);
+ }
+ else if (payload is ByteArray byteArray)
+ {
+ binaryPayload = byteArray.ToArray();
+ }
+ else if (payload is IEnumerable intArray)
+ {
+ binaryPayload = intArray.Select(Convert.ToByte).ToArray();
+ }
+ else
+ {
+ throw new NotSupportedException("Payload type not supported.");
+ }
+
+ applicationMessageBuilder = applicationMessageBuilder
+ .WithPayload(binaryPayload);
+
+ var applicationMessage = applicationMessageBuilder.Build();
+
+ _mqttServer.PublishAsync(applicationMessage).GetAwaiter().GetResult();
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while publishing application message from server.");
+ }
+ }
+
+ private IMqttServerOptions CreateMqttServerOptions()
+ {
+ var options = new MqttServerOptionsBuilder()
+ .WithMaxPendingMessagesPerClient(_settings.MaxPendingMessagesPerClient)
+ .WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(_settings.CommunicationTimeout))
+ .WithConnectionValidator(_mqttConnectionValidator)
+ .WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor)
+ .WithSubscriptionInterceptor(_mqttSubscriptionInterceptor)
+ .WithStorage(_mqttServerStorage);
+
+ // Configure unencrypted connections
+ if (_settings.TcpEndPoint.Enabled)
+ {
+ options.WithDefaultEndpoint();
+
+ if (_settings.TcpEndPoint.TryReadIPv4(out var address4))
+ {
+ options.WithDefaultEndpointBoundIPAddress(address4);
+ }
+
+ if (_settings.TcpEndPoint.TryReadIPv6(out var address6))
+ {
+ options.WithDefaultEndpointBoundIPV6Address(address6);
+ }
+
+ if (_settings.TcpEndPoint.Port > 0)
+ {
+ options.WithDefaultEndpointPort(_settings.TcpEndPoint.Port);
+ }
+ }
+ else
+ {
+ options.WithoutDefaultEndpoint();
+ }
+
+ // Configure encrypted connections
+ if (_settings.EncryptedTcpEndPoint.Enabled)
+ {
+ options
+ .WithEncryptedEndpoint()
+ .WithEncryptionSslProtocol(SslProtocols.Tls12)
+ .WithEncryptionCertificate(_settings.EncryptedTcpEndPoint.ReadCertificate());
+
+ if (_settings.EncryptedTcpEndPoint.TryReadIPv4(out var address4))
+ {
+ options.WithEncryptedEndpointBoundIPAddress(address4);
+ }
+
+ if (_settings.EncryptedTcpEndPoint.TryReadIPv6(out var address6))
+ {
+ options.WithEncryptedEndpointBoundIPV6Address(address6);
+ }
+
+ if (_settings.EncryptedTcpEndPoint.Port > 0)
+ {
+ options.WithEncryptedEndpointPort(_settings.EncryptedTcpEndPoint.Port);
+ }
+ }
+ else
+ {
+ options.WithoutEncryptedEndpoint();
+ }
+
+ if (_settings.ConnectionBacklog > 0)
+ {
+ options.WithConnectionBacklog(_settings.ConnectionBacklog);
+ }
+
+ if (_settings.EnablePersistentSessions)
+ {
+ options.WithPersistentSessions();
+ }
+
+ return options.Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerStorage.cs b/Source/MQTTnet.Server/Mqtt/MqttServerStorage.cs
new file mode 100644
index 00000000..39b65d51
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttServerStorage.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Server.Configuration;
+using Newtonsoft.Json;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttServerStorage : IMqttServerStorage
+ {
+ private readonly List _messages = new List();
+
+ private readonly SettingsModel _settings;
+ private readonly ILogger _logger;
+
+ private string _filename;
+ private bool _messagesHaveChanged;
+
+ public MqttServerStorage(SettingsModel settings, ILogger logger)
+ {
+ _settings = settings ?? throw new ArgumentNullException(nameof(settings));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public void Configure()
+ {
+ if (_settings.RetainedApplicationMessages?.Persist != true)
+ {
+ _logger.LogInformation("Persisting of retained application messages is disabled.");
+ return;
+ }
+
+ _filename = ExpandFilename();
+
+ // The retained application messages are stored in a separate thread.
+ // This is mandatory because writing them to a slow storage (like RaspberryPi SD card)
+ // will slow down the whole message processing speed.
+ Task.Run(SaveRetainedMessagesInternalAsync, CancellationToken.None);
+ }
+
+ public Task SaveRetainedMessagesAsync(IList messages)
+ {
+ lock (_messages)
+ {
+ _messages.Clear();
+ _messages.AddRange(messages);
+
+ _messagesHaveChanged = true;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private async Task SaveRetainedMessagesInternalAsync()
+ {
+ while (true)
+ {
+ try
+ {
+ await Task.Delay(TimeSpan.FromSeconds(_settings.RetainedApplicationMessages.WriteInterval)).ConfigureAwait(false);
+
+ List messages;
+ lock (_messages)
+ {
+ if (!_messagesHaveChanged)
+ {
+ continue;
+ }
+
+ messages = new List(_messages);
+ _messagesHaveChanged = false;
+ }
+
+ var json = JsonConvert.SerializeObject(messages);
+ await File.WriteAllTextAsync(_filename, json, Encoding.UTF8).ConfigureAwait(false);
+
+ _logger.LogInformation($"{messages.Count} retained MQTT messages written.");
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while writing retained MQTT messages.");
+ }
+ }
+ }
+
+ public async Task> LoadRetainedMessagesAsync()
+ {
+ if (_settings.RetainedApplicationMessages?.Persist != true)
+ {
+ return null;
+ }
+
+ if (!File.Exists(_filename))
+ {
+ return null;
+ }
+
+ try
+ {
+ var json = await File.ReadAllTextAsync(_filename).ConfigureAwait(false);
+ var applicationMessages = JsonConvert.DeserializeObject>(json);
+
+ _logger.LogInformation($"{applicationMessages.Count} retained MQTT messages loaded.");
+
+ return applicationMessages;
+ }
+ catch (Exception exception)
+ {
+ _logger.LogWarning(exception, "Error while loading persisted retained application messages.");
+ return null;
+ }
+ }
+
+ private string ExpandFilename()
+ {
+ var filename = _settings.RetainedApplicationMessages.Filename;
+
+ var uri = new Uri(filename, UriKind.RelativeOrAbsolute);
+ if (!uri.IsAbsoluteUri)
+ {
+ filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filename);
+ }
+
+ return filename;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs
new file mode 100644
index 00000000..2d37f747
--- /dev/null
+++ b/Source/MQTTnet.Server/Mqtt/MqttSubscriptionInterceptor.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Threading.Tasks;
+using IronPython.Runtime;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Server.Scripting;
+
+namespace MQTTnet.Server.Mqtt
+{
+ public class MqttSubscriptionInterceptor : IMqttServerSubscriptionInterceptor
+ {
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public MqttSubscriptionInterceptor(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context)
+ {
+ try
+ {
+ var pythonContext = new PythonDictionary
+ {
+ { "accept_subscription", context.AcceptSubscription },
+ { "close_connection", context.CloseConnection },
+
+ { "client_id", context.ClientId },
+ { "topic", context.TopicFilter.Topic },
+ { "qos", (int)context.TopicFilter.QualityOfServiceLevel }
+ };
+
+ _pythonScriptHostService.InvokeOptionalFunction("on_intercept_subscription", pythonContext);
+
+ context.AcceptSubscription = (bool)pythonContext["accept_subscription"];
+ context.CloseConnection = (bool)pythonContext["close_connection"];
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Error while intercepting subscription.");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Program.cs b/Source/MQTTnet.Server/Program.cs
new file mode 100644
index 00000000..3e8a0a0a
--- /dev/null
+++ b/Source/MQTTnet.Server/Program.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Reflection;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+
+namespace MQTTnet.Server
+{
+ public static class Program
+ {
+ public static int Main(string[] args)
+ {
+ try
+ {
+ PrintLogo();
+
+ WebHost.CreateDefaultBuilder(args).UseStartup().Build().Run();
+
+ return 0;
+ }
+ catch (Exception exception)
+ {
+ Console.WriteLine(exception);
+ return -1;
+ }
+ }
+
+ private static void PrintLogo()
+ {
+ Console.ResetColor();
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.BackgroundColor = ConsoleColor.Red;
+ const string LogoText =
+@"| |
+| __ __ ____ _______ _______ _ _____ |
+| | \/ |/ __ \__ __|__ __| | | / ____| |
+| | \ / | | | | | | | |_ __ ___| |_ | (___ ___ _ ____ _____ _ __ |
+| | |\/| | | | | | | | | '_ \ / _ \ __| \___ \ / _ \ '__\ \ / / _ \ '__| |
+| | | | | |__| | | | | | | | | __/ |_ ____) | __/ | \ V / __/ | |
+| |_| |_|\___\_\ |_| |_|_| |_|\___|\__| |_____/ \___|_| \_/ \___|_| |
+| |
+| |";
+
+ Console.WriteLine(LogoText);
+ Console.ResetColor();
+ Console.WriteLine();
+ 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: {Assembly.GetExecutingAssembly().GetName().Version}
+ License: MIT (read LICENSE file)
+ Sponsoring: https://opencollective.com/mqttnet
+ Support: https://github.com/chkr1011/MQTTnet/issues
+ Docs: https://github.com/chkr1011/MQTTnet/wiki/MQTTnetServer
+");
+
+ Console.BackgroundColor = ConsoleColor.White;
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine(" ! THIS IS AN ALPHA VERSION! IT IS NOT RECOMMENDED TO USE IT FOR ANY DIFFERENT PURPOSE THAN TESTING OR EVALUATING!");
+ Console.WriteLine();
+ Console.ResetColor();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/RetainedApplicationMessages.json b/Source/MQTTnet.Server/RetainedApplicationMessages.json
new file mode 100644
index 00000000..04c16db3
--- /dev/null
+++ b/Source/MQTTnet.Server/RetainedApplicationMessages.json
@@ -0,0 +1 @@
+[{"Topic":"a","Payload":"YWFzYXNhc2E=","QualityOfServiceLevel":0,"Retain":true,"UserProperties":[],"ContentType":null,"ResponseTopic":null,"PayloadFormatIndicator":null,"MessageExpiryInterval":null,"TopicAlias":null,"CorrelationData":null,"SubscriptionIdentifier":null},{"Topic":"fgdfgd","Payload":"YWFzYXNhc2E=","QualityOfServiceLevel":0,"Retain":true,"UserProperties":[],"ContentType":null,"ResponseTopic":null,"PayloadFormatIndicator":null,"MessageExpiryInterval":null,"TopicAlias":null,"CorrelationData":null,"SubscriptionIdentifier":null}]
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs b/Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs
new file mode 100644
index 00000000..41b6854b
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripting/DataSharing/DataSharingService.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+
+namespace MQTTnet.Server.Scripting.DataSharing
+{
+ public class DataSharingService
+ {
+ private readonly Dictionary _storage = new Dictionary();
+ private readonly PythonScriptHostService _pythonScriptHostService;
+ private readonly ILogger _logger;
+
+ public DataSharingService(PythonScriptHostService pythonScriptHostService, ILogger logger)
+ {
+ _pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public void Configure()
+ {
+ _pythonScriptHostService.RegisterProxyObject("write_shared_data", new Action(Write));
+ _pythonScriptHostService.RegisterProxyObject("read_shared_data", new Func(Read));
+ }
+
+ public void Write(string key, object value)
+ {
+ lock (_storage)
+ {
+ _storage[key] = value;
+ _logger.LogInformation($"Shared data with key '{key}' updated.");
+ }
+ }
+
+ public object Read(string key, object defaultValue)
+ {
+ lock (_storage)
+ {
+ if (!_storage.TryGetValue(key, out var value))
+ {
+ return defaultValue;
+ }
+
+ return value;
+ }
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Scripting/PythonConvert.cs b/Source/MQTTnet.Server/Scripting/PythonConvert.cs
new file mode 100644
index 00000000..532b691e
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripting/PythonConvert.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections;
+using System.Text;
+using IronPython.Runtime;
+
+namespace MQTTnet.Server.Scripting
+{
+ public static class PythonConvert
+ {
+ public static object ToPython(object value)
+ {
+ if (value is PythonDictionary)
+ {
+ return value;
+ }
+
+ if (value is string)
+ {
+ return value;
+ }
+
+ if (value is int)
+ {
+ return value;
+ }
+
+ if (value is float)
+ {
+ return value;
+ }
+
+ if (value is bool)
+ {
+ return value;
+ }
+
+ if (value is IDictionary dictionary)
+ {
+ var pythonDictionary = new PythonDictionary();
+ foreach (DictionaryEntry dictionaryEntry in dictionary)
+ {
+ pythonDictionary.Add(dictionaryEntry.Key, ToPython(dictionaryEntry.Value));
+ }
+
+ return pythonDictionary;
+ }
+
+ if (value is IEnumerable enumerable)
+ {
+ var pythonList = new List();
+ foreach (var item in enumerable)
+ {
+ pythonList.Add(ToPython(item));
+ }
+
+ return pythonList;
+ }
+
+ return value;
+ }
+
+ public static string Pythonfy(Enum value)
+ {
+ return Pythonfy(value.ToString());
+ }
+
+ public static string Pythonfy(string value)
+ {
+ var result = new StringBuilder();
+ foreach (var @char in value)
+ {
+ if (char.IsUpper(@char) && result.Length > 0)
+ {
+ result.Append('_');
+ }
+
+ result.Append(char.ToLowerInvariant(@char));
+ }
+
+ return result.ToString();
+ }
+
+ public static TEnum ParseEnum(string value) where TEnum : Enum
+ {
+ return (TEnum)Enum.Parse(typeof(TEnum), value.Replace("_", string.Empty), true);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Scripting/PythonIOStream.cs b/Source/MQTTnet.Server/Scripting/PythonIOStream.cs
new file mode 100644
index 00000000..b346e896
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripting/PythonIOStream.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.Logging;
+
+namespace MQTTnet.Server.Scripting
+{
+ public class PythonIOStream : Stream
+ {
+ private readonly ILogger _logger;
+ private readonly Encoding _encoder = Encoding.UTF8;
+
+ public PythonIOStream(ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null) throw new ArgumentNullException(nameof(buffer));
+
+ if (count == 0)
+ {
+ return;
+ }
+
+ var text = _encoder.GetString(buffer, offset, count);
+ if (text.Equals(Environment.NewLine))
+ {
+ return;
+ }
+
+ _logger.LogDebug(text);
+ }
+
+ public override bool CanRead { get; } = false;
+ public override bool CanSeek { get; } = false;
+ public override bool CanWrite { get; } = true;
+ public override long Length { get; } = 0L;
+
+ public override long Position
+ {
+ get => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs b/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs
new file mode 100644
index 00000000..1acc58c4
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripting/PythonScriptHostService.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.Extensions.Logging;
+using Microsoft.Scripting;
+using Microsoft.Scripting.Hosting;
+
+namespace MQTTnet.Server.Scripting
+{
+ public class PythonScriptHostService
+ {
+ private readonly IDictionary _proxyObjects = new ExpandoObject();
+ private readonly List _scriptInstances = new List();
+ private readonly ILogger _logger;
+ private readonly ScriptEngine _scriptEngine;
+
+ public PythonScriptHostService(PythonIOStream pythonIOStream, ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+
+ _scriptEngine = IronPython.Hosting.Python.CreateEngine();
+ _scriptEngine.Runtime.IO.SetOutput(pythonIOStream, Encoding.UTF8);
+ }
+
+ public void Configure()
+ {
+ AddSearchPaths(_scriptEngine);
+
+ var scriptsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts");
+ foreach (var filename in Directory.GetFiles(scriptsDirectory, "*.py", SearchOption.AllDirectories).OrderBy(file => file))
+ {
+ TryInitializeScript(filename);
+ }
+ }
+
+ public void RegisterProxyObject(string name, object @object)
+ {
+ if (name == null) throw new ArgumentNullException(nameof(name));
+ if (@object == null) throw new ArgumentNullException(nameof(@object));
+
+ _proxyObjects.Add(name, @object);
+ }
+
+ public void InvokeOptionalFunction(string name, object parameters)
+ {
+ if (name == null) throw new ArgumentNullException(nameof(name));
+
+ lock (_scriptInstances)
+ {
+ foreach (var pythonScriptInstance in _scriptInstances)
+ {
+ try
+ {
+ pythonScriptInstance.InvokeOptionalFunction(name, parameters);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, $"Error while invoking function '{name}' at script '{pythonScriptInstance.Name}'.");
+ }
+ }
+ }
+ }
+
+ private void TryInitializeScript(string filename)
+ {
+ try
+ {
+ var scriptName = new FileInfo(filename).Name;
+
+ _logger.LogTrace($"Initializing Python script '{scriptName}'...");
+ var code = File.ReadAllText(filename);
+
+ var scriptInstance = CreateScriptInstance(scriptName, code);
+ scriptInstance.InvokeOptionalFunction("initialize");
+
+ _scriptInstances.Add(scriptInstance);
+
+ _logger.LogInformation($"Initialized script '{scriptName}'.");
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, $"Error while initializing script '{new FileInfo(filename).Name}'.");
+ }
+ }
+
+ private PythonScriptInstance CreateScriptInstance(string name, string code)
+ {
+ var scriptScope = _scriptEngine.CreateScope();
+
+ var source = scriptScope.Engine.CreateScriptSourceFromString(code, SourceCodeKind.File);
+ var compiledCode = source.Compile();
+
+ scriptScope.SetVariable("mqtt_net_server", _proxyObjects);
+ compiledCode.Execute(scriptScope);
+
+ return new PythonScriptInstance(name, scriptScope);
+ }
+
+ private void AddSearchPaths(ScriptEngine scriptEngine)
+ {
+ var paths = new List
+ {
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Lib"),
+ "/usr/lib/python2.7",
+ @"C:\Python27\Lib"
+ };
+
+ AddSearchPaths(scriptEngine, paths);
+ }
+
+ private void AddSearchPaths(ScriptEngine scriptEngine, IEnumerable paths)
+ {
+ var searchPaths = scriptEngine.GetSearchPaths();
+
+ foreach (var path in paths)
+ {
+ if (Directory.Exists(path))
+ {
+ searchPaths.Add(path);
+ _logger.LogInformation($"Added Python lib path: {path}");
+ }
+ }
+
+ scriptEngine.SetSearchPaths(searchPaths);
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs b/Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs
new file mode 100644
index 00000000..91a46340
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripting/PythonScriptInstance.cs
@@ -0,0 +1,57 @@
+using System;
+using IronPython.Runtime;
+using Microsoft.Scripting.Hosting;
+
+namespace MQTTnet.Server.Scripting
+{
+ public class PythonScriptInstance
+ {
+ private readonly ScriptScope _scriptScope;
+
+ public PythonScriptInstance(string name, ScriptScope scriptScope)
+ {
+ _scriptScope = scriptScope;
+
+ Name = name;
+ }
+
+ public string Name { get; }
+
+ public bool InvokeOptionalFunction(string name, params object[] parameters)
+ {
+ return InvokeOptionalFunction(name, parameters, out _);
+ }
+
+ public bool InvokeOptionalFunction(string name, object[] parameters, out object result)
+ {
+ if (name == null) throw new ArgumentNullException(nameof(name));
+
+ lock (_scriptScope)
+ {
+ if (!_scriptScope.Engine.Operations.TryGetMember(_scriptScope, name, out var member))
+ {
+ result = null;
+ return false;
+ }
+
+ if (!(member is PythonFunction function))
+ {
+ throw new Exception($"Member '{name}' is no Python function.");
+ }
+
+ try
+ {
+ result = _scriptScope.Engine.Operations.Invoke(function, parameters);
+ return true;
+ }
+ catch (Exception exception)
+ {
+ var details = _scriptScope.Engine.GetService().FormatException(exception);
+ var message = $"Error while invoking function '{name}'. " + Environment.NewLine + details;
+
+ throw new Exception(message, exception);
+ }
+ }
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/Scripts/00_sample.py b/Source/MQTTnet.Server/Scripts/00_sample.py
new file mode 100644
index 00000000..845a2164
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripts/00_sample.py
@@ -0,0 +1,107 @@
+import json
+
+
+def initialize():
+ """
+ This function is invoked after the script file has been loaded.
+ It will be executed only one time.
+ """
+
+ print("Hello World from Sample script.")
+
+
+def on_validate_client_connection(context):
+ """
+ This function is invoked whenever a client wants to connect. It can be used to validate the connection.
+ """
+
+ print(context)
+
+ mqtt_net_server.write_shared_data(context["client_id"], {"custom_value_1": 1, "custom_value_2": True})
+
+ return
+
+ if context["client_id"] != "test_client":
+ context["result"] = "connection_refused_not_authorized"
+ return
+
+ if context["username"] != "bud spencer":
+ context["result"] = "connection_refused_not_authorized"
+ return
+
+ if context["password"] != "secret":
+ context["result"] = "connection_refused_not_authorized"
+
+ print(context)
+
+
+def on_intercept_subscription(context):
+ """
+ This function is invoked whenever a client wants to subscribe to a topic.
+ """
+
+ print("Client '{client_id}' want's to subscribe to topic '{topic}'.".format(client_id=context["client_id"], topic=context["topic"]))
+
+
+def on_intercept_application_message(context):
+ """
+ This function is invoked for every processed application message. It also allows modifying
+ the message or cancel processing at all.
+ """
+
+ client_id = context["client_id"]
+
+ if client_id != None:
+ shared_data = mqtt_net_server.read_shared_data(context["client_id"], {})
+ print(shared_data)
+
+ if context["topic"] == "topic_with_response":
+
+ json_payload = {
+ "hello": "world",
+ "x": 1,
+ "y": True,
+ "z": None
+ }
+
+ application_message = {
+ "retain": False,
+ "topic": "reply",
+ "payload": json.dumps(json_payload)
+ }
+
+ mqtt_net_server.publish(application_message)
+
+ print("Client '{client_id}' published topic '{topic}'.".format(client_id=context["client_id"], topic=context["topic"]))
+
+
+def on_client_connected(event_args):
+ """
+ This function is called whenever a client has passed the validation is connected.
+ """
+
+ print("Client '{client_id}' is now connected.".format(client_id=event_args["client_id"]))
+
+
+def on_client_disconnected(event_args):
+ """
+ This function is called whenever a client has disconnected.
+ """
+
+ print("Client '{client_id}' is now disconnected (type = {type}).".format(client_id=event_args["client_id"], type=event_args["type"]))
+
+
+def on_client_subscribed_topic(event_args):
+ """
+ This function is called whenever a client has subscribed to a topic (when allowed).
+ """
+
+ print("Client '{client_id}' has subscribed to '{topic}'.".format(client_id=event_args["client_id"], topic=event_args["topic"]))
+
+
+def on_client_unsubscribed_topic(event_args):
+ """
+ This function is called whenever a client has unsubscribed from a topic.
+ """
+
+ print("Client '{client_id}' has unsubscribed from '{topic}'.".format(client_id=event_args["client_id"], topic=event_args["topic"]))
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/Scripts/readme.md b/Source/MQTTnet.Server/Scripts/readme.md
new file mode 100644
index 00000000..fb4cc996
--- /dev/null
+++ b/Source/MQTTnet.Server/Scripts/readme.md
@@ -0,0 +1,19 @@
+This directory contains scripts which are loaded by the server and can be used to perform the following tasks.
+
+1. Validation of client connections via credentials, client IDs etc.
+2. Manipulation of every processed message.
+3. Validation of subscription attempts.
+4. Publishing of custom application messages.
+
+All scripts are loaded and _MQTTnet Server_ will invoke functions according to predefined naming conventions.
+If a function is implemented in multiple scripts the context will be moved throug all instances. This allows overriding of results or passing data to other (following) scripts.
+
+The Python starndard library ships with _MQTTnet Server_. But it is possible to add custom paths with Python libraries.
+
+```
+import sys
+sys.path.append(PATH_TO_LIBRARY)
+```
+
+* All scripts must have the file extension _.py_.
+* All scripts are sorted alphabetically (A to Z) before being loaded and parsed.
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs
new file mode 100644
index 00000000..7135508e
--- /dev/null
+++ b/Source/MQTTnet.Server/Startup.cs
@@ -0,0 +1,183 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.OpenApi.Models;
+using Microsoft.Scripting.Utils;
+using MQTTnet.Server.Configuration;
+using MQTTnet.Server.Logging;
+using MQTTnet.Server.Mqtt;
+using MQTTnet.Server.Scripting;
+using MQTTnet.Server.Scripting.DataSharing;
+using Swashbuckle.AspNetCore.SwaggerUI;
+
+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,
+ SettingsModel settings)
+ {
+ if (environment.IsDevelopment())
+ {
+ application.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ application.UseHsts();
+ }
+
+ application.UseStaticFiles();
+
+ application.UseHttpsRedirection();
+ application.UseMvc();
+
+ ConfigureWebSocketEndpoint(application, mqttServerService, settings);
+
+ dataSharingService.Configure();
+ pythonScriptHostService.Configure();
+
+ mqttServerService.Configure();
+
+ application.UseSwagger(o => o.RouteTemplate = "/api/{documentName}/swagger.json");
+
+ application.UseSwaggerUI(o =>
+ {
+ o.RoutePrefix = "api";
+ o.DocumentTitle = "MQTTnet.Server API";
+ o.SwaggerEndpoint("/api/v1/swagger.json", "MQTTnet.Server API v1");
+ o.DisplayRequestDuration();
+ o.DocExpansion(DocExpansion.List);
+ o.DefaultModelRendering(ModelRendering.Model);
+ });
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddMvc()
+ .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
+ .AddJsonOptions(options =>
+ {
+ options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
+ });
+
+ services.AddSingleton(ReadSettings());
+
+ 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.AddSwaggerGen(c =>
+ {
+ c.DescribeAllEnumsAsStrings();
+ c.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "MQTTnet.Server API",
+ Version = "v1",
+ Description = "The public API for the MQTT broker MQTTnet.Server.",
+ License = new OpenApiLicense
+ {
+ Name = "MIT",
+ Url = new Uri("https://github.com/chkr1011/MQTTnet/blob/master/README.md")
+ },
+ Contact = new OpenApiContact
+ {
+ Name = "MQTTnet.Server",
+ Email = string.Empty,
+ Url = new Uri("https://github.com/chkr1011/MQTTnet")
+ },
+ });
+ });
+ }
+
+ private SettingsModel ReadSettings()
+ {
+ var settings = new Configuration.SettingsModel();
+ Configuration.Bind("MQTT", settings);
+ return settings;
+ }
+
+ private static void ConfigureWebSocketEndpoint(
+ IApplicationBuilder application,
+ MqttServerService mqttServerService,
+ SettingsModel settings)
+ {
+ if (settings?.WebSocketEndPoint?.Enabled != true)
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(settings.WebSocketEndPoint.Path))
+ {
+ return;
+ }
+
+ var webSocketOptions = new WebSocketOptions
+ {
+ KeepAliveInterval = TimeSpan.FromSeconds(settings.WebSocketEndPoint.KeepAliveInterval),
+ ReceiveBufferSize = settings.WebSocketEndPoint.ReceiveBufferSize
+ };
+
+ if (settings.WebSocketEndPoint.AllowedOrigins?.Any() == true)
+ {
+ webSocketOptions.AllowedOrigins.AddRange(settings.WebSocketEndPoint.AllowedOrigins);
+ }
+
+ application.UseWebSockets(webSocketOptions);
+
+ application.Use(async (context, next) =>
+ {
+ if (context.Request.Path == settings.WebSocketEndPoint.Path)
+ {
+ if (context.WebSockets.IsWebSocketRequest)
+ {
+ using (var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false))
+ {
+ await mqttServerService.RunWebSocketConnectionAsync(webSocket, context).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ context.Response.StatusCode = 400;
+ }
+ }
+ else
+ {
+ await next().ConfigureAwait(false);
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/appsettings.Development.json b/Source/MQTTnet.Server/appsettings.Development.json
new file mode 100644
index 00000000..e203e940
--- /dev/null
+++ b/Source/MQTTnet.Server/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/Source/MQTTnet.Server/appsettings.json b/Source/MQTTnet.Server/appsettings.json
new file mode 100644
index 00000000..2ee0eaf3
--- /dev/null
+++ b/Source/MQTTnet.Server/appsettings.json
@@ -0,0 +1,58 @@
+{
+ "Kestrel": {
+ "EndPoints": {
+ "Http": {
+ "Url": "http://localhost:80"
+ },
+ "Https": {
+ "Url": "http://localhost:443"
+ }
+ }
+ },
+ "MQTT": {
+ /*
+ Wildcard Addresses:
+ * - All local IP addresses
+ localhost - Localhost only
+ disable - Skip address assignment
+ */
+ "TcpEndPoint": {
+ "Enabled": true,
+ "IPv4": "*",
+ "IPv6": "*",
+ "Port": 1883
+ },
+ "EncryptedTcpEndPoint": {
+ "Enabled": false,
+ "IPv4": "*",
+ "IPv6": "*",
+ "Port": 8883,
+ "CertificatePath": "/absolute/path/to/pfx"
+ },
+ "WebSocketEndPoint": {
+ "Enabled": true,
+ "Path": "/mqtt",
+ "KeepAliveInterval": 120, // In seconds.
+ "ReceiveBufferSize": 4096,
+ "AllowedOrigins": [] // List of strings with URLs.
+ },
+ "CommunicationTimeout": 15, // In seconds.
+ "ConnectionBacklog": 0, /* Set 0 to disable */
+ "EnablePersistentSessions": false,
+ "MaxPendingMessagesPerClient": 250,
+ "RetainedApplicationMessages": {
+ "Persist": true,
+ "Filename": "RetainedApplicationMessages.json",
+ "WriteInterval": 10 // In seconds.
+ },
+ "EnableDebugLogging": true
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/Source/MQTTnet.Server/package.json b/Source/MQTTnet.Server/package.json
new file mode 100644
index 00000000..5b2909f1
--- /dev/null
+++ b/Source/MQTTnet.Server/package.json
@@ -0,0 +1,12 @@
+{
+ "version": "1.0.0",
+ "name": "mqtt.test",
+ "private": true,
+ "devDependencies": {},
+ "dependencies": {
+ "@types/node": "^8.0.46",
+ "mqtt": "^2.15.1",
+ "systemjs": "^0.20.19",
+ "typescript": "^2.3.4"
+ }
+}
diff --git a/Source/MQTTnet.Server/run.bat b/Source/MQTTnet.Server/run.bat
new file mode 100644
index 00000000..016447e6
--- /dev/null
+++ b/Source/MQTTnet.Server/run.bat
@@ -0,0 +1,2 @@
+@echo off
+START "MQTTnet Server" dotnet .\MQTTnet.Server.dll
\ No newline at end of file
diff --git a/Source/MQTTnet.Server/run.sh b/Source/MQTTnet.Server/run.sh
new file mode 100644
index 00000000..1f755ad2
--- /dev/null
+++ b/Source/MQTTnet.Server/run.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+echo "Starting MQTTnet Server.."
+dotnet .\MQTTnet.Server.dll
\ No newline at end of file
diff --git a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs
index 43e5c8c4..08bc8098 100644
--- a/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs
+++ b/Source/MQTTnet/Adapter/IMqttChannelAdapter.cs
@@ -1,8 +1,8 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Formatter;
using MQTTnet.Packets;
-using MQTTnet.Serializer;
namespace MQTTnet.Adapter
{
@@ -10,17 +10,23 @@ namespace MQTTnet.Adapter
{
string Endpoint { get; }
- IMqttPacketSerializer PacketSerializer { get; }
+ bool IsSecureConnection { get; }
- event EventHandler ReadingPacketStarted;
+ MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
- event EventHandler ReadingPacketCompleted;
+ long BytesSent { get; }
+
+ long BytesReceived { get; }
+
+ Action ReadingPacketStartedCallback { get; set; }
+
+ Action ReadingPacketCompletedCallback { get; set; }
Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
- Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken);
+ Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken);
Task ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
}
diff --git a/Source/MQTTnet/Client/IMqttClientAdapterFactory.cs b/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs
similarity index 76%
rename from Source/MQTTnet/Client/IMqttClientAdapterFactory.cs
rename to Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs
index 197858b8..cb0988d8 100644
--- a/Source/MQTTnet/Client/IMqttClientAdapterFactory.cs
+++ b/Source/MQTTnet/Adapter/IMqttClientAdapterFactory.cs
@@ -1,7 +1,7 @@
-using MQTTnet.Adapter;
+using MQTTnet.Client.Options;
using MQTTnet.Diagnostics;
-namespace MQTTnet.Client
+namespace MQTTnet.Adapter
{
public interface IMqttClientAdapterFactory
{
diff --git a/Source/MQTTnet/Adapter/IMqttServerAdapter.cs b/Source/MQTTnet/Adapter/IMqttServerAdapter.cs
index eff9eab7..52cda51d 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;
+ Func ClientHandler { get; set; }
Task StartAsync(IMqttServerOptions options);
Task StopAsync();
diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs
index 13e66065..a4d71b39 100644
--- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs
+++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs
@@ -7,9 +7,9 @@ using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
-using MQTTnet.Serializer;
namespace MQTTnet.Adapter
{
@@ -22,41 +22,53 @@ namespace MQTTnet.Adapter
private readonly IMqttNetChildLogger _logger;
private readonly IMqttChannel _channel;
+ private readonly MqttPacketReader _packetReader;
private readonly byte[] _fixedHeaderBuffer = new byte[2];
- private readonly byte[] _singleByteBuffer = new byte[1];
-
private bool _isDisposed;
- public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger)
+ private long _bytesReceived;
+ private long _bytesSent;
+
+ public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetChildLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
- PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
+ PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
+
+ _packetReader = new MqttPacketReader(_channel);
_logger = logger.CreateChildLogger(nameof(MqttChannelAdapter));
}
public string Endpoint => _channel.Endpoint;
- public IMqttPacketSerializer PacketSerializer { get; }
+ public bool IsSecureConnection => _channel.IsSecureConnection;
- public event EventHandler ReadingPacketStarted;
- public event EventHandler ReadingPacketCompleted;
+ public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
+ public long BytesSent => Interlocked.Read(ref _bytesSent);
+ public long BytesReceived => Interlocked.Read(ref _bytesReceived);
+
+ public Action ReadingPacketStartedCallback { get; set; }
+ public Action ReadingPacketCompletedCallback { get; set; }
+
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
ThrowIfDisposed();
try
{
- _logger.Verbose("Connecting [Timeout={0}]", timeout);
-
- await Internal.TaskExtensions
- .TimeoutAfterAsync(ct => _channel.ConnectAsync(ct), timeout, cancellationToken)
- .ConfigureAwait(false);
+ if (timeout == TimeSpan.Zero)
+ {
+ await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await MqttTaskTimeout.WaitAsync(t => _channel.ConnectAsync(t), timeout, cancellationToken).ConfigureAwait(false);
+ }
}
catch (Exception exception)
{
@@ -75,11 +87,15 @@ namespace MQTTnet.Adapter
try
{
- _logger.Verbose("Disconnecting [Timeout={0}]", timeout);
-
- await Internal.TaskExtensions
- .TimeoutAfterAsync(ct => _channel.DisconnectAsync(), timeout, cancellationToken)
- .ConfigureAwait(false);
+ if (timeout == TimeSpan.Zero)
+ {
+ await _channel.DisconnectAsync(cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await MqttTaskTimeout.WaitAsync(
+ t => _channel.DisconnectAsync(t), timeout, cancellationToken).ConfigureAwait(false);
+ }
}
catch (Exception exception)
{
@@ -92,16 +108,28 @@ namespace MQTTnet.Adapter
}
}
- public async Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
+ public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
{
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
- var packetData = PacketSerializer.Serialize(packet);
- await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
- PacketSerializer.FreeBuffer();
+ var packetData = PacketFormatterAdapter.Encode(packet);
- _logger.Verbose("TX >>> {0}", packet);
+ if (timeout == TimeSpan.Zero)
+ {
+ await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await MqttTaskTimeout.WaitAsync(
+ t => _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, t), timeout, cancellationToken).ConfigureAwait(false);
+ }
+
+ Interlocked.Add(ref _bytesReceived, packetData.Count);
+
+ PacketFormatterAdapter.FreeBuffer();
+
+ _logger.Verbose("TX ({0} bytes) >>> {1}", packetData.Count, packet);
}
catch (Exception exception)
{
@@ -125,14 +153,13 @@ namespace MQTTnet.Adapter
try
{
ReceivedMqttPacket receivedMqttPacket;
-
- if (timeout > TimeSpan.Zero)
+ if (timeout == TimeSpan.Zero)
{
- receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false);
+ receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
}
else
{
- receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ receivedMqttPacket = await MqttTaskTimeout.WaitAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false);
}
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
@@ -140,13 +167,20 @@ namespace MQTTnet.Adapter
return null;
}
- var packet = PacketSerializer.Deserialize(receivedMqttPacket);
+ Interlocked.Add(ref _bytesSent, receivedMqttPacket.TotalLength);
+
+ if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
+ {
+ PacketFormatterAdapter.DetectProtocolVersion(receivedMqttPacket);
+ }
+
+ var packet = PacketFormatterAdapter.Decode(receivedMqttPacket);
if (packet == null)
{
throw new MqttProtocolViolationException("Received malformed packet.");
}
- _logger.Verbose("RX <<< {0}", packet);
+ _logger.Verbose("RX ({0} bytes) <<< {1}", receivedMqttPacket.TotalLength, packet);
return packet;
}
@@ -166,17 +200,34 @@ namespace MQTTnet.Adapter
return null;
}
+ public void ResetStatistics()
+ {
+ Interlocked.Exchange(ref _bytesReceived, 0L);
+ Interlocked.Exchange(ref _bytesSent, 0L);
+ }
+
private async Task ReceiveAsync(CancellationToken cancellationToken)
{
- var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(_channel, _fixedHeaderBuffer, _singleByteBuffer, cancellationToken).ConfigureAwait(false);
+ var readFixedHeaderResult = await _packetReader.ReadFixedHeaderAsync(_fixedHeaderBuffer, cancellationToken).ConfigureAwait(false);
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return null;
+ }
try
{
- ReadingPacketStarted?.Invoke(this, EventArgs.Empty);
+ if (readFixedHeaderResult.ConnectionClosed)
+ {
+ return null;
+ }
+ ReadingPacketStartedCallback?.Invoke();
+
+ var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0)
{
- return new ReceivedMqttPacket(fixedHeader.Flags, null);
+ return new ReceivedMqttPacket(fixedHeader.Flags, null, 2);
}
var body = new byte[fixedHeader.RemainingLength];
@@ -191,25 +242,27 @@ namespace MQTTnet.Adapter
chunkSize = bytesLeft;
}
-#if WINDOWS_UWP
var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false);
-#else
- // async/await is not used to avoid the overhead of context switches. We assume that the reamining data
- // has been sent from the sender directly after the initial bytes.
- var readBytes = _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult();
-#endif
- cancellationToken.ThrowIfCancellationRequested();
- ExceptionHelper.ThrowIfGracefulSocketClose(readBytes);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return null;
+ }
+
+ if (readBytes == 0)
+ {
+ return null;
+ }
bodyOffset += readBytes;
} while (bodyOffset < body.Length);
- return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0, body.Length));
+ var bodyReader = new MqttPacketBodyReader(body, 0, body.Length);
+ return new ReceivedMqttPacket(fixedHeader.Flags, bodyReader, fixedHeader.TotalLength);
}
finally
{
- ReadingPacketCompleted?.Invoke(this, EventArgs.Empty);
+ ReadingPacketCompletedCallback?.Invoke();
}
}
@@ -230,15 +283,19 @@ namespace MQTTnet.Adapter
private static bool IsWrappedException(Exception exception)
{
- return exception is TaskCanceledException ||
- exception is OperationCanceledException ||
+ return exception is OperationCanceledException ||
exception is MqttCommunicationTimedOutException ||
exception is MqttCommunicationException;
}
private static void WrapException(Exception exception)
{
- if (exception is IOException && exception.InnerException is SocketException socketException)
+ if (exception is IOException && exception.InnerException is SocketException innerException)
+ {
+ exception = innerException;
+ }
+
+ if (exception is SocketException socketException)
{
if (socketException.SocketErrorCode == SocketError.ConnectionAborted ||
socketException.SocketErrorCode == SocketError.OperationAborted)
diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs
index 68445cfd..44d50ec1 100644
--- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs
+++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs
@@ -1,16 +1,16 @@
-using MQTTnet.Exceptions;
-using MQTTnet.Protocol;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Exceptions;
namespace MQTTnet.Adapter
{
public class MqttConnectingFailedException : MqttCommunicationException
{
- public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
- : base($"Connecting with MQTT server failed ({returnCode}).")
+ public MqttConnectingFailedException(MqttClientConnectResultCode resultCode)
+ : base($"Connecting with MQTT server failed ({resultCode.ToString()}).")
{
- ReturnCode = returnCode;
+ ResultCode = resultCode;
}
- public MqttConnectReturnCode ReturnCode { get; }
+ public MqttClientConnectResultCode ResultCode { get; }
}
}
diff --git a/Source/MQTTnet/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs b/Source/MQTTnet/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
deleted file mode 100644
index 42852493..00000000
--- a/Source/MQTTnet/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace MQTTnet.Adapter
-{
- public class MqttServerAdapterClientAcceptedEventArgs : EventArgs
- {
- public MqttServerAdapterClientAcceptedEventArgs(IMqttChannelAdapter client)
- {
- Client = client ?? throw new ArgumentNullException(nameof(client));
- }
-
- public IMqttChannelAdapter Client { get; }
-
- public Task SessionTask { get; set; }
- }
-}
diff --git a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs
index 3a92d4de..355ad96f 100644
--- a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs
+++ b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs
@@ -1,17 +1,20 @@
-using MQTTnet.Serializer;
+using MQTTnet.Formatter;
namespace MQTTnet.Adapter
{
public class ReceivedMqttPacket
{
- public ReceivedMqttPacket(byte fixedHeader, MqttPacketBodyReader body)
+ public ReceivedMqttPacket(byte fixedHeader, IMqttPacketBodyReader body, int totalLength)
{
FixedHeader = fixedHeader;
Body = body;
+ TotalLength = totalLength;
}
- public byte FixedHeader { get; }
+ public byte FixedHeader { get; set; }
- public MqttPacketBodyReader Body { get; }
+ public IMqttPacketBodyReader Body { get; }
+
+ public int TotalLength { get; }
}
}
diff --git a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs b/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs
deleted file mode 100644
index 81dddbf1..00000000
--- a/Source/MQTTnet/ApplicationMessagePublisherExtensions.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using MQTTnet.Protocol;
-
-namespace MQTTnet
-{
- public static class ApplicationMessagePublisherExtensions
- {
- public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable applicationMessages)
- {
- if (publisher == null) throw new ArgumentNullException(nameof(publisher));
- if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
-
- foreach (var applicationMessage in applicationMessages)
- {
- await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
- }
- }
-
- public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
- {
- if (publisher == null) throw new ArgumentNullException(nameof(publisher));
- if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
-
- foreach (var applicationMessage in applicationMessages)
- {
- await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
- }
- }
-
- public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
- {
- if (publisher == null) throw new ArgumentNullException(nameof(publisher));
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- return publisher.PublishAsync(builder => builder
- .WithTopic(topic));
- }
-
- public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
- {
- if (publisher == null) throw new ArgumentNullException(nameof(publisher));
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- return publisher.PublishAsync(builder => builder
- .WithTopic(topic)
- .WithPayload(payload));
- }
-
- public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
- {
- if (publisher == null) throw new ArgumentNullException(nameof(publisher));
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- return publisher.PublishAsync(builder => builder
- .WithTopic(topic)
- .WithPayload(payload)
- .WithQualityOfServiceLevel(qualityOfServiceLevel));
- }
-
- public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
- {
- if (publisher == null) throw new ArgumentNullException(nameof(publisher));
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- return publisher.PublishAsync(builder => builder
- .WithTopic(topic)
- .WithPayload(payload)
- .WithQualityOfServiceLevel(qualityOfServiceLevel)
- .WithRetainFlag(retain));
- }
-
- public static Task PublishAsync(this IApplicationMessagePublisher publisher, Func builder)
- {
- var message = builder(new MqttApplicationMessageBuilder()).Build();
- return publisher.PublishAsync(message);
- }
- }
-}
diff --git a/Source/MQTTnet/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs
index 6050bcb1..4848b46d 100644
--- a/Source/MQTTnet/Channel/IMqttChannel.cs
+++ b/Source/MQTTnet/Channel/IMqttChannel.cs
@@ -7,9 +7,10 @@ namespace MQTTnet.Channel
public interface IMqttChannel : IDisposable
{
string Endpoint { get; }
+ bool IsSecureConnection { get; }
Task ConnectAsync(CancellationToken cancellationToken);
- Task DisconnectAsync();
+ Task DisconnectAsync(CancellationToken cancellationToken);
Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
diff --git a/Source/MQTTnet/Client/Connecting/IMqttClientConnectedHandler.cs b/Source/MQTTnet/Client/Connecting/IMqttClientConnectedHandler.cs
new file mode 100644
index 00000000..8b7be27e
--- /dev/null
+++ b/Source/MQTTnet/Client/Connecting/IMqttClientConnectedHandler.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace MQTTnet.Client.Connecting
+{
+ public interface IMqttClientConnectedHandler
+ {
+ Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs);
+ }
+}
diff --git a/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs b/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs
new file mode 100644
index 00000000..db6e5be5
--- /dev/null
+++ b/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs
@@ -0,0 +1,9 @@
+namespace MQTTnet.Client.Connecting
+{
+ public class MqttClientAuthenticateResult
+ {
+ public bool IsSessionPresent { get; set; }
+
+ public MqttClientConnectResultCode ResultCode { get; set; }
+ }
+}
diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs
new file mode 100644
index 00000000..a0dbae7b
--- /dev/null
+++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResultCode.cs
@@ -0,0 +1,28 @@
+namespace MQTTnet.Client.Connecting
+{
+ public enum MqttClientConnectResultCode
+ {
+ Success = 0,
+ UnspecifiedError = 128,
+ MalformedPacket = 129,
+ ProtocolError = 130,
+ ImplementationSpecificError = 131,
+ UnsupportedProtocolVersion = 132,
+ ClientIdentifierNotValid = 133,
+ BadUserNameOrPassword = 134,
+ NotAuthorized = 135,
+ ServerUnavailable = 136,
+ ServerBusy = 137,
+ Banned = 138,
+ BadAuthenticationMethod = 140,
+ TopicNameInvalid = 144,
+ PacketTooLarge = 149,
+ QuotaExceeded = 151,
+ PayloadFormatInvalid = 153,
+ RetainNotSupported = 154,
+ QoSNotSupported = 155,
+ UseAnotherServer = 156,
+ ServerMoved = 157,
+ ConnectionRateExceeded = 159
+ }
+}
diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs
new file mode 100644
index 00000000..e7f97b9e
--- /dev/null
+++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace MQTTnet.Client.Connecting
+{
+ public class MqttClientConnectedEventArgs : EventArgs
+ {
+ public MqttClientConnectedEventArgs(MqttClientAuthenticateResult authenticateResult)
+ {
+ AuthenticateResult = authenticateResult ?? throw new ArgumentNullException(nameof(authenticateResult));
+ }
+
+ public MqttClientAuthenticateResult AuthenticateResult { get; }
+ }
+}
diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedHandlerDelegate.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedHandlerDelegate.cs
new file mode 100644
index 00000000..96812afe
--- /dev/null
+++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedHandlerDelegate.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Client.Connecting
+{
+ public class MqttClientConnectedHandlerDelegate : IMqttClientConnectedHandler
+ {
+ private readonly Func _handler;
+
+ public MqttClientConnectedHandlerDelegate(Action handler)
+ {
+ if (handler == null) throw new ArgumentNullException(nameof(handler));
+
+ _handler = context =>
+ {
+ handler(context);
+ return Task.FromResult(0);
+ };
+ }
+
+ public MqttClientConnectedHandlerDelegate(Func handler)
+ {
+ _handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ }
+
+ public Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs)
+ {
+ return _handler(eventArgs);
+ }
+ }
+}
diff --git a/Source/MQTTnet/Client/Disconnecting/IMqttClientDisconnectedHandler.cs b/Source/MQTTnet/Client/Disconnecting/IMqttClientDisconnectedHandler.cs
new file mode 100644
index 00000000..14347bf0
--- /dev/null
+++ b/Source/MQTTnet/Client/Disconnecting/IMqttClientDisconnectedHandler.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace MQTTnet.Client.Disconnecting
+{
+ public interface IMqttClientDisconnectedHandler
+ {
+ Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs);
+ }
+}
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
new file mode 100644
index 00000000..995806e7
--- /dev/null
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
@@ -0,0 +1,9 @@
+namespace MQTTnet.Client.Disconnecting
+{
+ public class MqttClientDisconnectOptions
+ {
+ public MqttClientDisconnectReason ReasonCode { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
+
+ public string ReasonString { get; set; }
+ }
+}
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs
new file mode 100644
index 00000000..3e1bb661
--- /dev/null
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectReason.cs
@@ -0,0 +1,8 @@
+namespace MQTTnet.Client.Disconnecting
+{
+ public enum MqttClientDisconnectReason
+ {
+ NormalDisconnection = 0,
+
+ }
+}
diff --git a/Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs
similarity index 90%
rename from Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs
rename to Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs
index 2161cc26..7506042c 100644
--- a/Source/MQTTnet/Client/MqttClientDisconnectedEventArgs.cs
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Disconnecting
{
public class MqttClientDisconnectedEventArgs : EventArgs
{
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedHandlerDelegate.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedHandlerDelegate.cs
new file mode 100644
index 00000000..c02443ed
--- /dev/null
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedHandlerDelegate.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Client.Disconnecting
+{
+ public class MqttClientDisconnectedHandlerDelegate : IMqttClientDisconnectedHandler
+ {
+ private readonly Func _handler;
+
+ public MqttClientDisconnectedHandlerDelegate(Action handler)
+ {
+ if (handler == null) throw new ArgumentNullException(nameof(handler));
+
+ _handler = context =>
+ {
+ handler(context);
+ return Task.FromResult(0);
+ };
+ }
+
+ public MqttClientDisconnectedHandlerDelegate(Func handler)
+ {
+ _handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ }
+
+ public Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs)
+ {
+ return _handler(eventArgs);
+ }
+ }
+}
diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs
index b60a31a8..70259cf6 100644
--- a/Source/MQTTnet/Client/IMqttClient.cs
+++ b/Source/MQTTnet/Client/IMqttClient.cs
@@ -1,6 +1,11 @@
using System;
-using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
+using MQTTnet.Client.Options;
+using MQTTnet.Client.Subscribing;
+using MQTTnet.Client.Unsubscribing;
namespace MQTTnet.Client
{
@@ -9,13 +14,14 @@ namespace MQTTnet.Client
bool IsConnected { get; }
IMqttClientOptions Options { get; }
- event EventHandler Connected;
- event EventHandler Disconnected;
+ IMqttClientConnectedHandler ConnectedHandler { get; set; }
+
+ IMqttClientDisconnectedHandler DisconnectedHandler { get; set; }
+
+ Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken);
+ Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
- Task ConnectAsync(IMqttClientOptions options);
- Task DisconnectAsync();
-
- Task> SubscribeAsync(IEnumerable topicFilters);
- Task UnsubscribeAsync(IEnumerable topics);
+ Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken);
+ Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/IMqttClientFactory.cs b/Source/MQTTnet/Client/IMqttClientFactory.cs
index cd687afc..95d567ea 100644
--- a/Source/MQTTnet/Client/IMqttClientFactory.cs
+++ b/Source/MQTTnet/Client/IMqttClientFactory.cs
@@ -1,4 +1,5 @@
-using MQTTnet.Diagnostics;
+using MQTTnet.Adapter;
+using MQTTnet.Diagnostics;
namespace MQTTnet.Client
{
diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs
index 0dc664da..10387bc7 100644
--- a/Source/MQTTnet/Client/MqttClient.cs
+++ b/Source/MQTTnet/Client/MqttClient.cs
@@ -1,13 +1,18 @@
using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
+using MQTTnet.Client.Options;
+using MQTTnet.Client.Publishing;
+using MQTTnet.Client.Receiving;
+using MQTTnet.Client.Subscribing;
+using MQTTnet.Client.Unsubscribing;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
-using MQTTnet.Internal;
+using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -16,16 +21,17 @@ namespace MQTTnet.Client
public class MqttClient : IMqttClient
{
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
+ private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly Stopwatch _sendTracker = new Stopwatch();
private readonly object _disconnectLock = new object();
- private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly IMqttNetChildLogger _logger;
- private CancellationTokenSource _cancellationTokenSource;
- internal Task _packetReceiverTask;
- internal Task _keepAliveMessageSenderTask;
+ private CancellationTokenSource _backgroundCancellationTokenSource;
+ private Task _packetReceiverTask;
+ private Task _keepAlivePacketsSenderTask;
+
private IMqttChannelAdapter _adapter;
private bool _cleanDisconnectInitiated;
private long _disconnectGate;
@@ -38,14 +44,17 @@ namespace MQTTnet.Client
_logger = logger.CreateChildLogger(nameof(MqttClient));
}
- public event EventHandler Connected;
- public event EventHandler Disconnected;
- public event EventHandler ApplicationMessageReceived;
+ public IMqttClientConnectedHandler ConnectedHandler { get; set; }
+
+ public IMqttClientDisconnectedHandler DisconnectedHandler { get; set; }
+
+ public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
public bool IsConnected { get; private set; }
+
public IMqttClientOptions Options { get; private set; }
- public async Task ConnectAsync(IMqttClientOptions options)
+ public async Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");
@@ -59,31 +68,39 @@ namespace MQTTnet.Client
_packetIdentifierProvider.Reset();
_packetDispatcher.Reset();
- _cancellationTokenSource = new CancellationTokenSource();
- _disconnectGate = 0;
- _adapter = _adapterFactory.CreateClientAdapter(options, _logger);
+ _backgroundCancellationTokenSource = new CancellationTokenSource();
+ var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
- _logger.Verbose($"Trying to connect with server ({Options.ChannelOptions}).");
- await _adapter.ConnectAsync(Options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
+ _disconnectGate = 0;
+ var adapter = _adapterFactory.CreateClientAdapter(options, _logger);
+ _adapter = adapter;
+
+ _logger.Verbose($"Trying to connect with server '{options.ChannelOptions}' (Timeout={options.CommunicationTimeout}).");
+ await _adapter.ConnectAsync(options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
_logger.Verbose("Connection with server established.");
- StartReceivingPackets(_cancellationTokenSource.Token);
+ _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
- var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false);
- _logger.Verbose("MQTT connection with server established.");
+ var authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, cancellationToken).ConfigureAwait(false);
_sendTracker.Restart();
if (Options.KeepAlivePeriod != TimeSpan.Zero)
{
- StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
+ _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
}
IsConnected = true;
- Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
_logger.Info("Connected.");
- return new MqttClientConnectResult(connectResponse.IsSessionPresent);
+
+ var connectedHandler = ConnectedHandler;
+ if (connectedHandler != null)
+ {
+ await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(authenticateResult)).ConfigureAwait(false);
+ }
+
+ return authenticateResult;
}
catch (Exception exception)
{
@@ -98,15 +115,16 @@ namespace MQTTnet.Client
}
}
- public async Task DisconnectAsync()
+ public async Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken)
{
try
{
_cleanDisconnectInitiated = true;
- if (IsConnected && !_cancellationTokenSource.IsCancellationRequested)
+ if (IsConnected)
{
- await SendAsync(new MqttDisconnectPacket(), _cancellationTokenSource.Token).ConfigureAwait(false);
+ var disconnectPacket = _adapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(options);
+ await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
}
}
finally
@@ -118,64 +136,55 @@ namespace MQTTnet.Client
}
}
- public async Task> SubscribeAsync(IEnumerable topicFilters)
+ public async Task SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken)
{
- if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+ if (options == null) throw new ArgumentNullException(nameof(options));
ThrowIfNotConnected();
- var subscribePacket = new MqttSubscribePacket
- {
- PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
- TopicFilters = topicFilters.ToList()
- };
+ var subscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateSubscribePacket(options);
+ subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
- var response = await SendAndReceiveAsync(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
-
- if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
- {
- throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
- }
-
- return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
+ var subAckPacket = await SendAndReceiveAsync(subscribePacket, cancellationToken).ConfigureAwait(false);
+ return _adapter.PacketFormatterAdapter.DataConverter.CreateClientSubscribeResult(subscribePacket, subAckPacket);
}
- public Task UnsubscribeAsync(IEnumerable topicFilters)
+ public async Task UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken)
{
- if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+ if (options == null) throw new ArgumentNullException(nameof(options));
ThrowIfNotConnected();
- var unsubscribePacket = new MqttUnsubscribePacket
- {
- PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
- TopicFilters = topicFilters.ToList()
- };
+ var unsubscribePacket = _adapter.PacketFormatterAdapter.DataConverter.CreateUnsubscribePacket(options);
+ unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
- return SendAndReceiveAsync(unsubscribePacket, _cancellationTokenSource.Token);
+ var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false);
+ return _adapter.PacketFormatterAdapter.DataConverter.CreateClientUnsubscribeResult(unsubscribePacket, unsubAckPacket);
}
- public Task PublishAsync(MqttApplicationMessage applicationMessage)
+ public Task PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
+ MqttTopicValidator.ThrowIfInvalid(applicationMessage.Topic);
+
ThrowIfNotConnected();
- var publishPacket = applicationMessage.ToPublishPacket();
+ var publishPacket = _adapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(applicationMessage);
switch (applicationMessage.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
- // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
- return SendAsync(publishPacket, _cancellationTokenSource.Token);
+ return PublishAtMostOnce(publishPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
- publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
- return SendAndReceiveAsync(publishPacket, _cancellationTokenSource.Token);
+ return PublishAtLeastOnceAsync(publishPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
- return PublishExactlyOnce(publishPacket, _cancellationTokenSource.Token);
+ return PublishExactlyOnceAsync(publishPacket, cancellationToken);
}
default:
{
@@ -186,33 +195,31 @@ namespace MQTTnet.Client
public void Dispose()
{
- _cancellationTokenSource?.Cancel(false);
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
+ _backgroundCancellationTokenSource?.Cancel(false);
+ _backgroundCancellationTokenSource?.Dispose();
+ _backgroundCancellationTokenSource = null;
_adapter?.Dispose();
_adapter = null;
}
- private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
+ private async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
{
- var connectPacket = new MqttConnectPacket
- {
- ClientId = Options.ClientId,
- Username = Options.Credentials?.Username,
- Password = Options.Credentials?.Password,
- CleanSession = Options.CleanSession,
- KeepAlivePeriod = (ushort)Options.KeepAlivePeriod.TotalSeconds,
- WillMessage = willApplicationMessage
- };
+ var connectPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnectPacket(
+ willApplicationMessage,
+ Options);
- var response = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false);
- if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
+ var connAckPacket = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false);
+ var result = channelAdapter.PacketFormatterAdapter.DataConverter.CreateClientConnectResult(connAckPacket);
+
+ if (result.ResultCode != MqttClientConnectResultCode.Success)
{
- throw new MqttConnectingFailedException(response.ConnectReturnCode);
+ throw new MqttConnectingFailedException(result.ResultCode);
}
- return response;
+ _logger.Verbose("Authenticated MQTT connection with server established.");
+
+ return result;
}
private void ThrowIfNotConnected()
@@ -230,19 +237,20 @@ namespace MQTTnet.Client
var clientWasConnected = IsConnected;
InitiateDisconnect();
-
+
IsConnected = false;
try
{
if (_adapter != null)
{
+ _logger.Verbose("Disconnecting [Timeout={0}]", Options.CommunicationTimeout);
await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false);
- await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false);
-
+ await WaitForTaskAsync(_keepAlivePacketsSenderTask, sender).ConfigureAwait(false);
+
_logger.Verbose("Disconnected from adapter.");
}
catch (Exception adapterException)
@@ -255,7 +263,12 @@ namespace MQTTnet.Client
_cleanDisconnectInitiated = false;
_logger.Info("Disconnected.");
- Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
+
+ var disconnectedHandler = DisconnectedHandler;
+ if (disconnectedHandler != null)
+ {
+ await disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception)).ConfigureAwait(false);
+ }
}
}
@@ -265,12 +278,12 @@ namespace MQTTnet.Client
{
try
{
- if (_cancellationTokenSource?.IsCancellationRequested == true)
+ if (_backgroundCancellationTokenSource?.IsCancellationRequested == true)
{
return;
}
- _cancellationTokenSource?.Cancel(false);
+ _backgroundCancellationTokenSource?.Cancel(false);
}
catch (Exception exception)
{
@@ -281,11 +294,14 @@ namespace MQTTnet.Client
private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromResult(0);
+ }
_sendTracker.Restart();
- return _adapter.SendPacketAsync(packet, cancellationToken);
+ return _adapter.SendPacketAsync(packet, Options.CommunicationTimeout, cancellationToken);
}
private async Task SendAndReceiveAsync(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
@@ -300,26 +316,22 @@ namespace MQTTnet.Client
identifier = packetWithIdentifier.PacketIdentifier.Value;
}
- var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier);
- try
+ using (var packetAwaiter = _packetDispatcher.AddPacketAwaiter(identifier))
{
- await _adapter.SendPacketAsync(requestPacket, cancellationToken).ConfigureAwait(false);
- var respone = await Internal.TaskExtensions.TimeoutAfterAsync(ct => packetAwaiter.Task, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
-
- return (TResponsePacket)respone;
- }
- catch (MqttCommunicationTimedOutException)
- {
- _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Namespace);
- throw;
- }
- finally
- {
- _packetDispatcher.RemovePacketAwaiter(identifier);
+ try
+ {
+ await _adapter.SendPacketAsync(requestPacket, Options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
+ return await packetAwaiter.WaitOneAsync(Options.CommunicationTimeout).ConfigureAwait(false);
+ }
+ catch (MqttCommunicationTimedOutException)
+ {
+ _logger.Warning(null, "Timeout while waiting for packet of type '{0}'.", typeof(TResponsePacket).Name);
+ throw;
+ }
}
}
- private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
+ private async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{
try
{
@@ -364,7 +376,7 @@ namespace MQTTnet.Client
if (!DisconnectIsPending())
{
- await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
+ await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception).ConfigureAwait(false);
}
}
finally
@@ -373,7 +385,7 @@ namespace MQTTnet.Client
}
}
- private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
+ private async Task TryReceivePacketsAsync(CancellationToken cancellationToken)
{
try
{
@@ -381,13 +393,24 @@ namespace MQTTnet.Client
while (!cancellationToken.IsCancellationRequested)
{
- var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken)
- .ConfigureAwait(false);
+ var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
- if (packet != null && !cancellationToken.IsCancellationRequested)
+ if (cancellationToken.IsCancellationRequested)
{
- await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
+ return;
}
+
+ if (packet == null)
+ {
+ if (!DisconnectIsPending())
+ {
+ await DisconnectInternalAsync(_packetReceiverTask, null).ConfigureAwait(false);
+ }
+
+ return;
+ }
+
+ await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
@@ -422,103 +445,99 @@ namespace MQTTnet.Client
}
}
- private Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
- {
- if (packet is MqttPublishPacket publishPacket)
- {
- return ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken);
- }
-
- if (packet is MqttPingReqPacket)
- {
- return SendAsync(new MqttPingRespPacket(), cancellationToken);
- }
-
- if (packet is MqttDisconnectPacket)
- {
- return DisconnectAsync();
- }
-
- if (packet is MqttPubRelPacket pubRelPacket)
- {
- return ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken);
- }
-
- _packetDispatcher.Dispatch(packet);
- return Task.FromResult(0);
- }
-
- private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
- {
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
- {
- FireApplicationMessageReceivedEvent(publishPacket);
- return Task.FromResult(0);
- }
-
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
- {
- FireApplicationMessageReceivedEvent(publishPacket);
- return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
- }
-
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
- {
- // QoS 2 is implement as method "B" (4.3.3 QoS 2: Exactly once delivery)
- FireApplicationMessageReceivedEvent(publishPacket);
- return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
- }
-
- throw new MqttCommunicationException("Received a not supported QoS level.");
- }
-
- private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
- {
- var response = new MqttPubCompPacket
- {
- PacketIdentifier = pubRelPacket.PacketIdentifier
- };
-
- return SendAsync(response, cancellationToken);
- }
-
- private async Task PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
- {
- publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
-
- var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false);
- var pubRelPacket = new MqttPubRelPacket
- {
- PacketIdentifier = pubRecPacket.PacketIdentifier
- };
-
- await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
- }
-
- private void StartReceivingPackets(CancellationToken cancellationToken)
- {
- _packetReceiverTask = Task.Factory.StartNew(
- () => ReceivePacketsAsync(cancellationToken),
- cancellationToken,
- TaskCreationOptions.LongRunning,
- TaskScheduler.Default).Unwrap();
- }
-
- private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
- {
- _keepAliveMessageSenderTask = Task.Factory.StartNew(
- () => SendKeepAliveMessagesAsync(cancellationToken),
- cancellationToken,
- TaskCreationOptions.LongRunning,
- TaskScheduler.Default).Unwrap();
- }
-
- private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
+ private async Task TryProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
try
{
- var applicationMessage = publishPacket.ToApplicationMessage();
- ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage));
+ if (packet is MqttPublishPacket publishPacket)
+ {
+ await TryProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+ }
+ else if (packet is MqttPubRelPacket pubRelPacket)
+ {
+ await SendAsync(new MqttPubCompPacket
+ {
+ PacketIdentifier = pubRelPacket.PacketIdentifier,
+ ReasonCode = MqttPubCompReasonCode.Success
+ }, cancellationToken).ConfigureAwait(false);
+ }
+ else if (packet is MqttPingReqPacket)
+ {
+ await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false);
+ }
+ else if (packet is MqttDisconnectPacket)
+ {
+ await DisconnectAsync(null, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ _packetDispatcher.Dispatch(packet);
+ }
+ }
+ catch (Exception exception)
+ {
+ if (_cleanDisconnectInitiated)
+ {
+ return;
+ }
+
+ if (exception is OperationCanceledException)
+ {
+ }
+ else if (exception is MqttCommunicationException)
+ {
+ _logger.Warning(exception, "MQTT communication exception while receiving packets.");
+ }
+ else
+ {
+ _logger.Error(exception, "Unhandled exception while receiving packets.");
+ }
+
+ _packetDispatcher.Dispatch(exception);
+
+ if (!DisconnectIsPending())
+ {
+ await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private async Task TryProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ try
+ {
+ if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
+ {
+ await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
+ }
+ else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
+ {
+ if (await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false))
+ {
+ await SendAsync(new MqttPubAckPacket
+ {
+ PacketIdentifier = publishPacket.PacketIdentifier,
+ ReasonCode = MqttPubAckReasonCode.Success
+ }, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
+ {
+ if (await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false))
+ {
+ var pubRecPacket = new MqttPubRecPacket
+ {
+ PacketIdentifier = publishPacket.PacketIdentifier,
+ ReasonCode = MqttPubRecReasonCode.Success
+ };
+
+ await SendAsync(pubRecPacket, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ throw new MqttProtocolViolationException("Received a not supported QoS level.");
+ }
}
catch (Exception exception)
{
@@ -526,6 +545,52 @@ namespace MQTTnet.Client
}
}
+ private async Task PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
+ await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+ return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(null);
+ }
+
+ private async Task PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
+ var response = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+ return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(response);
+ }
+
+ private async Task PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
+
+ var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false);
+
+ var pubRelPacket = new MqttPubRelPacket
+ {
+ PacketIdentifier = publishPacket.PacketIdentifier,
+ ReasonCode = MqttPubRelReasonCode.Success
+ };
+
+ var pubCompPacket = await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
+
+ return _adapter.PacketFormatterAdapter.DataConverter.CreatePublishResult(pubRecPacket, pubCompPacket);
+ }
+
+ private async Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
+ {
+ var applicationMessage = _adapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket);
+
+ var handler = ApplicationMessageReceivedHandler;
+ if (handler != null)
+ {
+ var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage);
+ await handler.HandleApplicationMessageReceivedAsync(eventArgs).ConfigureAwait(false);
+ return !eventArgs.ProcessingFailed;
+ }
+
+ return true;
+ }
+
private static async Task WaitForTaskAsync(Task task, Task sender)
{
if (task == sender || task == null)
diff --git a/Source/MQTTnet/Client/MqttClientConnectResult.cs b/Source/MQTTnet/Client/MqttClientConnectResult.cs
deleted file mode 100644
index 6044bf50..00000000
--- a/Source/MQTTnet/Client/MqttClientConnectResult.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace MQTTnet.Client
-{
- public class MqttClientConnectResult
- {
- public MqttClientConnectResult(bool isSessionPresent)
- {
- IsSessionPresent = isSessionPresent;
- }
-
- public bool IsSessionPresent { get; }
- }
-}
diff --git a/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs
deleted file mode 100644
index c8c6ce56..00000000
--- a/Source/MQTTnet/Client/MqttClientConnectedEventArgs.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace MQTTnet.Client
-{
- public class MqttClientConnectedEventArgs : EventArgs
- {
- public MqttClientConnectedEventArgs(bool isSessionPresent)
- {
- IsSessionPresent = isSessionPresent;
- }
-
- public bool IsSessionPresent { get; }
- }
-}
diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs
index a7548dee..e5e3cd75 100644
--- a/Source/MQTTnet/Client/MqttClientExtensions.cs
+++ b/Source/MQTTnet/Client/MqttClientExtensions.cs
@@ -1,22 +1,147 @@
using System;
using System.Collections.Generic;
-using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
+using MQTTnet.Client.Options;
+using MQTTnet.Client.Publishing;
+using MQTTnet.Client.Receiving;
+using MQTTnet.Client.Subscribing;
+using MQTTnet.Client.Unsubscribing;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
- public static Task> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters)
+ public static IMqttClient UseConnectedHandler(this IMqttClient client, Func handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseConnectedHandler((IMqttClientConnectedHandler)null);
+ }
+
+ return client.UseConnectedHandler(new MqttClientConnectedHandlerDelegate(handler));
+ }
+
+ public static IMqttClient UseConnectedHandler(this IMqttClient client, Action handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseConnectedHandler((IMqttClientConnectedHandler)null);
+ }
+
+ return client.UseConnectedHandler(new MqttClientConnectedHandlerDelegate(handler));
+ }
+
+ public static IMqttClient UseConnectedHandler(this IMqttClient client, IMqttClientConnectedHandler handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ client.ConnectedHandler = handler;
+ return client;
+ }
+
+ public static IMqttClient UseDisconnectedHandler(this IMqttClient client, Func handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseDisconnectedHandler((IMqttClientDisconnectedHandler)null);
+ }
+
+ return client.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(handler));
+ }
+
+ public static IMqttClient UseDisconnectedHandler(this IMqttClient client, Action handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseDisconnectedHandler((IMqttClientDisconnectedHandler)null);
+ }
+
+ return client.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(handler));
+ }
+
+ public static IMqttClient UseDisconnectedHandler(this IMqttClient client, IMqttClientDisconnectedHandler handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ client.DisconnectedHandler = handler;
+ return client;
+ }
+
+ public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, Func handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
+ }
+
+ return client.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
+ }
+
+ public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, Action handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (handler == null)
+ {
+ return client.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
+ }
+
+ return client.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
+ }
+
+ public static IMqttClient UseApplicationMessageReceivedHandler(this IMqttClient client, IMqttApplicationMessageReceivedHandler handler)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ client.ApplicationMessageReceivedHandler = handler;
+ return client;
+ }
+
+ public static Task ReconnectAsync(this IMqttClient client)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ if (client.Options == null)
+ {
+ throw new InvalidOperationException("_ReconnectAsync_ can be used only if _ConnectAsync_ was called before.");
+ }
+
+ return client.ConnectAsync(client.Options);
+ }
+
+ public static Task DisconnectAsync(this IMqttClient client)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+
+ return client.DisconnectAsync(null);
+ }
+
+ public static Task SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
- return client.SubscribeAsync(topicFilters.ToList());
+ var options = new MqttClientSubscribeOptions();
+ options.TopicFilters.AddRange(topicFilters);
+
+ return client.SubscribeAsync(options);
}
- public static Task> SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
+ public static Task SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
@@ -24,7 +149,7 @@ namespace MQTTnet.Client
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}
- public static Task> SubscribeAsync(this IMqttClient client, string topic)
+ public static Task SubscribeAsync(this IMqttClient client, string topic)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));
@@ -32,12 +157,123 @@ namespace MQTTnet.Client
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
}
- public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
+ public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
- return client.UnsubscribeAsync(topicFilters.ToList());
+ var options = new MqttClientUnsubscribeOptions();
+ options.TopicFilters.AddRange(topicFilters);
+
+ return client.UnsubscribeAsync(options);
+ }
+
+ public static Task ConnectAsync(this IMqttClient client, IMqttClientOptions options)
+ {
+ return client.ConnectAsync(options, CancellationToken.None);
+ }
+
+ public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectOptions options)
+ {
+ return client.DisconnectAsync(options, CancellationToken.None);
+ }
+
+ public static Task SubscribeAsync(this IMqttClient client, MqttClientSubscribeOptions options)
+ {
+ return client.SubscribeAsync(options, CancellationToken.None);
+ }
+
+ public static Task UnsubscribeAsync(this IMqttClient client, MqttClientUnsubscribeOptions options)
+ {
+ return client.UnsubscribeAsync(options, CancellationToken.None);
+ }
+
+ public static Task PublishAsync(this IMqttClient client, MqttApplicationMessage applicationMessage)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
+ return client.PublishAsync(applicationMessage, CancellationToken.None);
+ }
+
+ public static async Task PublishAsync(this IMqttClient client, IEnumerable applicationMessages)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
+
+ foreach (var applicationMessage in applicationMessages)
+ {
+ await client.PublishAsync(applicationMessage).ConfigureAwait(false);
+ }
+ }
+
+ public static Task PublishAsync(this IMqttClient client, string topic)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .Build());
+ }
+
+ public static Task PublishAsync(this IMqttClient client, string topic, IEnumerable payload)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .Build());
+ }
+
+ public static Task PublishAsync(this IMqttClient client, string topic, string payload)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .Build());
+ }
+
+ public static Task PublishAsync(this IMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .WithQualityOfServiceLevel(qualityOfServiceLevel)
+ .Build());
+ }
+
+ public static Task PublishAsync(this IMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .WithQualityOfServiceLevel(qualityOfServiceLevel)
+ .WithRetainFlag(retain)
+ .Build());
+ }
+
+ public static Task PublishAsync(this IMqttClient client, string topic, string payload, bool retain)
+ {
+ if (client == null) throw new ArgumentNullException(nameof(client));
+ if (topic == null) throw new ArgumentNullException(nameof(topic));
+
+ return client.PublishAsync(new MqttApplicationMessageBuilder()
+ .WithTopic(topic)
+ .WithPayload(payload)
+ .WithRetainFlag(retain)
+ .Build());
}
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs
index dc099183..bfee3861 100644
--- a/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs
+++ b/Source/MQTTnet/Client/MqttPacketIdentifierProvider.cs
@@ -13,7 +13,7 @@
}
}
- public ushort GetNewPacketIdentifier()
+ public ushort GetNextPacketIdentifier()
{
lock (_syncRoot)
{
diff --git a/Source/MQTTnet/Client/MqttSubscribeResult.cs b/Source/MQTTnet/Client/MqttSubscribeResult.cs
deleted file mode 100644
index 2b7c46da..00000000
--- a/Source/MQTTnet/Client/MqttSubscribeResult.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using MQTTnet.Protocol;
-
-namespace MQTTnet.Client
-{
- public class MqttSubscribeResult
- {
- public MqttSubscribeResult(TopicFilter topicFilter, MqttSubscribeReturnCode returnCode)
- {
- TopicFilter = topicFilter;
- ReturnCode = returnCode;
- }
-
- public TopicFilter TopicFilter { get; }
-
- public MqttSubscribeReturnCode ReturnCode { get; }
- }
-}
diff --git a/Source/MQTTnet/Client/IMqttClientChannelOptions.cs b/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs
similarity index 75%
rename from Source/MQTTnet/Client/IMqttClientChannelOptions.cs
rename to Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs
index 22249632..729d12f0 100644
--- a/Source/MQTTnet/Client/IMqttClientChannelOptions.cs
+++ b/Source/MQTTnet/Client/Options/IMqttClientChannelOptions.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public interface IMqttClientChannelOptions
{
diff --git a/Source/MQTTnet/Client/IMqttClientCredentials.cs b/Source/MQTTnet/Client/Options/IMqttClientCredentials.cs
similarity index 77%
rename from Source/MQTTnet/Client/IMqttClientCredentials.cs
rename to Source/MQTTnet/Client/Options/IMqttClientCredentials.cs
index d214d64f..ec188673 100644
--- a/Source/MQTTnet/Client/IMqttClientCredentials.cs
+++ b/Source/MQTTnet/Client/Options/IMqttClientCredentials.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public interface IMqttClientCredentials
{
diff --git a/Source/MQTTnet/Client/IMqttClientOptions.cs b/Source/MQTTnet/Client/Options/IMqttClientOptions.cs
similarity index 52%
rename from Source/MQTTnet/Client/IMqttClientOptions.cs
rename to Source/MQTTnet/Client/Options/IMqttClientOptions.cs
index 176f5e43..3150baa3 100644
--- a/Source/MQTTnet/Client/IMqttClientOptions.cs
+++ b/Source/MQTTnet/Client/Options/IMqttClientOptions.cs
@@ -1,7 +1,7 @@
using System;
-using MQTTnet.Serializer;
+using MQTTnet.Formatter;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public interface IMqttClientOptions
{
@@ -16,5 +16,15 @@ namespace MQTTnet.Client
TimeSpan? KeepAliveSendInterval { get; }
MqttApplicationMessage WillMessage { get; }
+ uint? WillDelayInterval { get; }
+
+ string AuthenticationMethod { get; }
+ byte[] AuthenticationData { get; }
+ uint? MaximumPacketSize { get; }
+ ushort? ReceiveMaximum { get; }
+ bool? RequestProblemInformation { get; }
+ bool? RequestResponseInformation { get; }
+ uint? SessionExpiryInterval { get; }
+ ushort? TopicAliasMaximum { get; }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/MqttClientCredentials.cs b/Source/MQTTnet/Client/Options/MqttClientCredentials.cs
similarity index 82%
rename from Source/MQTTnet/Client/MqttClientCredentials.cs
rename to Source/MQTTnet/Client/Options/MqttClientCredentials.cs
index feaa1e6c..8836e364 100644
--- a/Source/MQTTnet/Client/MqttClientCredentials.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientCredentials.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientCredentials : IMqttClientCredentials
{
diff --git a/Source/MQTTnet/Client/MqttClientOptions.cs b/Source/MQTTnet/Client/Options/MqttClientOptions.cs
similarity index 52%
rename from Source/MQTTnet/Client/MqttClientOptions.cs
rename to Source/MQTTnet/Client/Options/MqttClientOptions.cs
index 7d3ad5d8..be9dc7b0 100644
--- a/Source/MQTTnet/Client/MqttClientOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptions.cs
@@ -1,20 +1,31 @@
using System;
-using MQTTnet.Serializer;
+using MQTTnet.Formatter;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientOptions : IMqttClientOptions
{
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
public bool CleanSession { get; set; } = true;
- public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
+ public IMqttClientCredentials Credentials { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
- public IMqttClientChannelOptions ChannelOptions { get; set; }
+ public IMqttClientChannelOptions ChannelOptions { get; set; }
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
public TimeSpan? KeepAliveSendInterval { get; set; }
public MqttApplicationMessage WillMessage { get; set; }
+ public uint? WillDelayInterval { get; set; }
+
+ public string AuthenticationMethod { get; set; }
+ public byte[] AuthenticationData { get; set; }
+
+ public uint? MaximumPacketSize { get; set; }
+ public ushort? ReceiveMaximum { get; set; }
+ public bool? RequestProblemInformation { get; set; }
+ public bool? RequestResponseInformation { get; set; }
+ public uint? SessionExpiryInterval { get; set; }
+ public ushort? TopicAliasMaximum { get; set; }
}
}
diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
similarity index 70%
rename from Source/MQTTnet/Client/MqttClientOptionsBuilder.cs
rename to Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
index 85e2b4d8..20e008a3 100644
--- a/Source/MQTTnet/Client/MqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
@@ -1,8 +1,8 @@
using System;
using System.Linq;
-using MQTTnet.Serializer;
+using MQTTnet.Formatter;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientOptionsBuilder
{
@@ -15,6 +15,11 @@ namespace MQTTnet.Client
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{
+ if (value == MqttProtocolVersion.Unknown)
+ {
+ throw new ArgumentException("Protocol version is invalid.");
+ }
+
_options.ProtocolVersion = value;
return this;
}
@@ -31,6 +36,11 @@ namespace MQTTnet.Client
return this;
}
+ public MqttClientOptionsBuilder WithNoKeepAlive()
+ {
+ return WithKeepAlivePeriod(TimeSpan.Zero);
+ }
+
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = value;
@@ -55,6 +65,55 @@ namespace MQTTnet.Client
return this;
}
+ public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
+ {
+ _options.AuthenticationMethod = method;
+ _options.AuthenticationData = data;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithWillDelayInterval(uint? willDelayInterval)
+ {
+ _options.WillDelayInterval = willDelayInterval;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithTopicAliasMaximum(ushort? topicAliasMaximum)
+ {
+ _options.TopicAliasMaximum = topicAliasMaximum;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithMaximumPacketSize(uint? maximumPacketSize)
+ {
+ _options.MaximumPacketSize = maximumPacketSize;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithReceiveMaximum(ushort? receiveMaximum)
+ {
+ _options.ReceiveMaximum = receiveMaximum;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithRequestProblemInformation(bool? requestProblemInformation = true)
+ {
+ _options.RequestProblemInformation = requestProblemInformation;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithRequestResponseInformation(bool? requestResponseInformation = true)
+ {
+ _options.RequestResponseInformation = requestResponseInformation;
+ return this;
+ }
+
+ public MqttClientOptionsBuilder WithSessionExpiryInterval(uint? sessionExpiryInterval)
+ {
+ _options.SessionExpiryInterval = sessionExpiryInterval;
+ return this;
+ }
+
public MqttClientOptionsBuilder WithCredentials(string username, string password = null)
{
_options.Credentials = new MqttClientCredentials
@@ -92,11 +151,13 @@ namespace MQTTnet.Client
return this;
}
- public MqttClientOptionsBuilder WithWebSocketServer(string uri)
+ public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters = null)
{
_webSocketOptions = new MqttClientWebSocketOptions
{
- Uri = uri
+ Uri = uri,
+ RequestHeaders = parameters?.RequestHeaders,
+ CookieContainer = parameters?.CookieContainer
};
return this;
@@ -113,25 +174,6 @@ namespace MQTTnet.Client
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
}
- [Obsolete("Use method _WithTls_ which accepts the _MqttClientOptionsBuilderTlsParameters_.")]
- public MqttClientOptionsBuilder WithTls(
- bool allowUntrustedCertificates = false,
- bool ignoreCertificateChainErrors = false,
- bool ignoreCertificateRevocationErrors = false,
- params byte[][] certificates)
- {
- _tlsParameters = new MqttClientOptionsBuilderTlsParameters
- {
- UseTls = true,
- AllowUntrustedCertificates = allowUntrustedCertificates,
- IgnoreCertificateChainErrors = ignoreCertificateChainErrors,
- IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors,
- Certificates = certificates?.ToList()
- };
-
- return this;
- }
-
public IMqttClientOptions Build()
{
if (_tcpOptions == null && _webSocketOptions == null)
diff --git a/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
similarity index 96%
rename from Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs
rename to Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
index 5764c797..ea36baa9 100644
--- a/Source/MQTTnet/Client/MqttClientOptionsBuilderTlsParameters.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
@@ -4,7 +4,7 @@ using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientOptionsBuilderTlsParameters
{
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderWebSocketParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderWebSocketParameters.cs
new file mode 100644
index 00000000..f516e837
--- /dev/null
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderWebSocketParameters.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Net;
+
+namespace MQTTnet.Client.Options
+{
+ public class MqttClientOptionsBuilderWebSocketParameters
+ {
+ public IDictionary RequestHeaders { get; set; }
+
+ public CookieContainer CookieContainer { get; set; }
+ }
+}
diff --git a/Source/MQTTnet/Client/MqttClientTcpOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs
similarity index 54%
rename from Source/MQTTnet/Client/MqttClientTcpOptions.cs
rename to Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs
index 4569be33..c7b0e809 100644
--- a/Source/MQTTnet/Client/MqttClientTcpOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs
@@ -1,4 +1,6 @@
-namespace MQTTnet.Client
+using System.Net.Sockets;
+
+namespace MQTTnet.Client.Options
{
public class MqttClientTcpOptions : IMqttClientChannelOptions
{
@@ -6,7 +8,13 @@
public int? Port { get; set; }
- public int BufferSize { get; set; } = 4096;
+ public int BufferSize { get; set; } = 65536;
+
+ public bool DualMode { get; set; } = true;
+
+ public bool NoDelay { get; set; } = true;
+
+ public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
diff --git a/Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs b/Source/MQTTnet/Client/Options/MqttClientTcpOptionsExtensions.cs
similarity index 92%
rename from Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs
rename to Source/MQTTnet/Client/Options/MqttClientTcpOptionsExtensions.cs
index 460069a7..1b49a58d 100644
--- a/Source/MQTTnet/Client/MqttClientTcpOptionsExtensions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientTcpOptionsExtensions.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public static class MqttClientTcpOptionsExtensions
{
diff --git a/Source/MQTTnet/Client/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
similarity index 95%
rename from Source/MQTTnet/Client/MqttClientTlsOptions.cs
rename to Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
index 73d65cbc..db4077da 100644
--- a/Source/MQTTnet/Client/MqttClientTlsOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Net.Security;
-using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientTlsOptions
{
diff --git a/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs
similarity index 95%
rename from Source/MQTTnet/Client/MqttClientWebSocketOptions.cs
rename to Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs
index fe8f1d27..d62f1f17 100644
--- a/Source/MQTTnet/Client/MqttClientWebSocketOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketOptions.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Net;
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientWebSocketOptions : IMqttClientChannelOptions
{
diff --git a/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs
similarity index 90%
rename from Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs
rename to Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs
index 13b13667..7ff40f84 100644
--- a/Source/MQTTnet/Client/MqttClientWebSocketProxyOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientWebSocketProxyOptions.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Client
+namespace MQTTnet.Client.Options
{
public class MqttClientWebSocketProxyOptions
{
diff --git a/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs
new file mode 100644
index 00000000..7b485c6b
--- /dev/null
+++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishReasonCode.cs
@@ -0,0 +1,15 @@
+namespace MQTTnet.Client.Publishing
+{
+ public enum MqttClientPublishReasonCode
+ {
+ Success = 0,
+ NoMatchingSubscribers = 16,
+ UnspecifiedError = 128,
+ ImplementationSpecificError = 131,
+ NotAuthorized = 135,
+ TopicNameInvalid = 144,
+ PacketIdentifierInUse = 145,
+ QuotaExceeded = 151,
+ PayloadFormatInvalid = 153
+ }
+}
diff --git a/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs b/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs
new file mode 100644
index 00000000..15035ec0
--- /dev/null
+++ b/Source/MQTTnet/Client/Publishing/MqttClientPublishResult.cs
@@ -0,0 +1,17 @@
+
+using System.Collections.Generic;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Client.Publishing
+{
+ public class MqttClientPublishResult
+ {
+ public ushort? PacketIdentifier { get; set; }
+
+ public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success;
+
+ public string ReasonString { get; set; }
+
+ public List UserProperties { get; set; }
+ }
+}
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/MqttApplicationMessageReceivedHandlerDelegate.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs
new file mode 100644
index 00000000..1d73aaa1
--- /dev/null
+++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedHandlerDelegate.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Client.Receiving
+{
+ public class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler
+ {
+ private readonly Func _handler;
+
+ public MqttApplicationMessageReceivedHandlerDelegate(Action handler)
+ {
+ if (handler == null) throw new ArgumentNullException(nameof(handler));
+
+ _handler = context =>
+ {
+ handler(context);
+ return Task.FromResult(0);
+ };
+ }
+
+ public MqttApplicationMessageReceivedHandlerDelegate(Func handler)
+ {
+ _handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ }
+
+ public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs context)
+ {
+ return _handler(context);
+ }
+ }
+}
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs
new file mode 100644
index 00000000..8d2adbf8
--- /dev/null
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Client.Subscribing
+{
+ public class MqttClientSubscribeOptions
+ {
+ public List TopicFilters { get; set; } = new List();
+
+ public List UserProperties { get; set; } = new List();
+ }
+}
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs
new file mode 100644
index 00000000..66d68d4e
--- /dev/null
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResult.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace MQTTnet.Client.Subscribing
+{
+ public class MqttClientSubscribeResult
+ {
+ public List Items { get; } = new List();
+ }
+}
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs
new file mode 100644
index 00000000..119ec4fa
--- /dev/null
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultCode.cs
@@ -0,0 +1,18 @@
+namespace MQTTnet.Client.Subscribing
+{
+ public enum MqttClientSubscribeResultCode
+ {
+ GrantedQoS0 = 0,
+ GrantedQoS1 = 1,
+ GrantedQoS2 = 2,
+ UnspecifiedError = 128,
+ ImplementationSpecificError = 131,
+ NotAuthorized = 135,
+ TopicFilterInvalid = 143,
+ PacketIdentifierInUse = 145,
+ QuotaExceeded = 151,
+ SharedSubscriptionsNotSupported = 158,
+ SubscriptionIdentifiersNotSupported = 161,
+ WildcardSubscriptionsNotSupported = 162
+ }
+}
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs
new file mode 100644
index 00000000..286123f4
--- /dev/null
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeResultItem.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace MQTTnet.Client.Subscribing
+{
+ public class MqttClientSubscribeResultItem
+ {
+ public MqttClientSubscribeResultItem(TopicFilter topicFilter, MqttClientSubscribeResultCode resultCode)
+ {
+ TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
+ ResultCode = resultCode;
+ }
+
+ public TopicFilter TopicFilter { get; }
+
+ public MqttClientSubscribeResultCode ResultCode { get; }
+ }
+}
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs
new file mode 100644
index 00000000..73c21ef7
--- /dev/null
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Client.Unsubscribing
+{
+ public class MqttClientUnsubscribeOptions
+ {
+ public List TopicFilters { get; set; } = new List();
+
+ public List UserProperties { get; set; } = new List();
+ }
+}
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs
new file mode 100644
index 00000000..f3e4c4c9
--- /dev/null
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResult.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace MQTTnet.Client.Unsubscribing
+{
+ public class MqttClientUnsubscribeResult
+ {
+ public List Items { get; } =new List();
+ }
+}
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs
new file mode 100644
index 00000000..269abc10
--- /dev/null
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultCode.cs
@@ -0,0 +1,13 @@
+namespace MQTTnet.Client.Unsubscribing
+{
+ public enum MqttClientUnsubscribeResultCode
+ {
+ Success = 0,
+ NoSubscriptionExisted = 17,
+ UnspecifiedError = 128,
+ ImplementationSpecificError = 131,
+ NotAuthorized = 135,
+ TopicFilterInvalid = 143,
+ PacketIdentifierInUse = 145
+ }
+}
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs
new file mode 100644
index 00000000..755ee43c
--- /dev/null
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeResultItem.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace MQTTnet.Client.Unsubscribing
+{
+ public class MqttClientUnsubscribeResultItem
+ {
+ public MqttClientUnsubscribeResultItem(string topicFilter, MqttClientUnsubscribeResultCode reasonCode)
+ {
+ TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
+ ReasonCode = reasonCode;
+ }
+
+ public string TopicFilter { get; }
+
+ public MqttClientUnsubscribeResultCode ReasonCode { get; }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs b/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs
deleted file mode 100644
index 21a66b90..00000000
--- a/Source/MQTTnet/Exceptions/MqttCommunicationClosedGracefullyException.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace MQTTnet.Exceptions
-{
- public class MqttCommunicationClosedGracefullyException : MqttCommunicationException
- {
- }
-}
diff --git a/Source/MQTTnet/Formatter/IMqttDataConverter.cs b/Source/MQTTnet/Formatter/IMqttDataConverter.cs
new file mode 100644
index 00000000..c4540e73
--- /dev/null
+++ b/Source/MQTTnet/Formatter/IMqttDataConverter.cs
@@ -0,0 +1,37 @@
+using MQTTnet.Client.Connecting;
+using MQTTnet.Client.Disconnecting;
+using MQTTnet.Client.Options;
+using MQTTnet.Client.Publishing;
+using MQTTnet.Client.Subscribing;
+using MQTTnet.Client.Unsubscribing;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Formatter
+{
+ public interface IMqttDataConverter
+ {
+ MqttPublishPacket CreatePublishPacket(MqttApplicationMessage applicationMessage);
+
+ MqttPubAckPacket CreatePubAckPacket(MqttPublishPacket publishPacket);
+
+ MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket);
+
+ MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
+
+ MqttConnectPacket CreateConnectPacket(MqttApplicationMessage willApplicationMessage, IMqttClientOptions options);
+
+ MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
+
+ MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket);
+
+ MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options);
+
+ MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options);
+
+ MqttDisconnectPacket CreateDisconnectPacket(MqttClientDisconnectOptions options);
+
+ MqttClientPublishResult CreatePublishResult(MqttPubAckPacket pubAckPacket);
+
+ MqttClientPublishResult CreatePublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
+ }
+}
diff --git a/Source/MQTTnet/Formatter/IMqttPacketBodyReader.cs b/Source/MQTTnet/Formatter/IMqttPacketBodyReader.cs
new file mode 100644
index 00000000..632c4931
--- /dev/null
+++ b/Source/MQTTnet/Formatter/IMqttPacketBodyReader.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace MQTTnet.Formatter
+{
+ public interface IMqttPacketBodyReader
+ {
+ ulong Length { get; }
+
+ ulong Offset { get; }
+
+ bool EndOfStream { get; }
+
+ byte ReadByte();
+
+ byte[] ReadRemainingData();
+
+ ushort ReadTwoByteInteger();
+
+ string ReadStringWithLengthPrefix();
+
+ byte[] ReadWithLengthPrefix();
+
+ uint ReadFourByteInteger();
+
+ uint ReadVariableLengthInteger();
+
+ bool ReadBoolean();
+
+ void Seek(ulong position);
+ }
+}
diff --git a/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs
new file mode 100644
index 00000000..6dd417c5
--- /dev/null
+++ b/Source/MQTTnet/Formatter/IMqttPacketFormatter.cs
@@ -0,0 +1,17 @@
+using System;
+using MQTTnet.Adapter;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Formatter
+{
+ public interface IMqttPacketFormatter
+ {
+ IMqttDataConverter DataConverter { get; }
+
+ ArraySegment Encode(MqttBasePacket mqttPacket);
+
+ MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket);
+
+ void FreeBuffer();
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Formatter/IMqttPacketWriter.cs b/Source/MQTTnet/Formatter/IMqttPacketWriter.cs
new file mode 100644
index 00000000..cce98cf5
--- /dev/null
+++ b/Source/MQTTnet/Formatter/IMqttPacketWriter.cs
@@ -0,0 +1,20 @@
+namespace MQTTnet.Formatter
+{
+ public interface IMqttPacketWriter
+ {
+ int Length { get; }
+
+ void WriteWithLengthPrefix(string value);
+ void Write(byte returnCode);
+ void WriteWithLengthPrefix(byte[] payload);
+ void Write(ushort keepAlivePeriod);
+
+ void Write(IMqttPacketWriter propertyWriter);
+ void WriteVariableLengthInteger(uint length);
+ void Write(byte[] payload, int v, int length);
+ void Reset(int v);
+ void Seek(int v);
+ void FreeBuffer();
+ byte[] GetBuffer();
+ }
+}
diff --git a/Source/MQTTnet/Serializer/MqttFixedHeader.cs b/Source/MQTTnet/Formatter/MqttFixedHeader.cs
similarity index 53%
rename from Source/MQTTnet/Serializer/MqttFixedHeader.cs
rename to Source/MQTTnet/Formatter/MqttFixedHeader.cs
index a8c2015b..4c693337 100644
--- a/Source/MQTTnet/Serializer/MqttFixedHeader.cs
+++ b/Source/MQTTnet/Formatter/MqttFixedHeader.cs
@@ -1,15 +1,18 @@
-namespace MQTTnet.Serializer
+namespace MQTTnet.Formatter
{
public struct MqttFixedHeader
{
- public MqttFixedHeader(byte flags, int remainingLength)
+ public MqttFixedHeader(byte flags, int remainingLength, int totalLength)
{
Flags = flags;
RemainingLength = remainingLength;
+ TotalLength = totalLength;
}
public byte Flags { get; }
public int RemainingLength { get; }
+
+ public int TotalLength { get; }
}
}
diff --git a/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs b/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs
new file mode 100644
index 00000000..c45fb3d9
--- /dev/null
+++ b/Source/MQTTnet/Formatter/MqttPacketBodyReader.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using MQTTnet.Exceptions;
+
+namespace MQTTnet.Formatter
+{
+ public class MqttPacketBodyReader : IMqttPacketBodyReader
+ {
+ private readonly byte[] _buffer;
+ private readonly ulong _initialOffset;
+ private readonly ulong _length;
+
+ public MqttPacketBodyReader(byte[] buffer, int offset, int length)
+ : this(buffer, (ulong)offset, (ulong)length)
+ {
+ }
+
+ public MqttPacketBodyReader(byte[] buffer, ulong offset, ulong length)
+ {
+ _buffer = buffer;
+ _initialOffset = offset;
+ Offset = offset;
+ _length = length;
+ }
+
+ public ulong Offset { get; private set; }
+
+ public ulong Length => _length - Offset;
+
+ public bool EndOfStream => Offset == _length;
+
+ public void Seek(ulong position)
+ {
+ Offset = _initialOffset + position;
+ }
+
+ public ArraySegment Read(uint length)
+ {
+ ValidateReceiveBuffer(length);
+
+ var buffer = new ArraySegment(_buffer, (int)Offset, (int)length);
+ Offset += length;
+ return buffer;
+ }
+
+ public byte ReadByte()
+ {
+ ValidateReceiveBuffer(1);
+ return _buffer[Offset++];
+ }
+
+ public bool ReadBoolean()
+ {
+ ValidateReceiveBuffer(1);
+ var buffer = _buffer[Offset++];
+
+ if (buffer == 0)
+ {
+ return false;
+ }
+
+ if (buffer == 1)
+ {
+ return true;
+ }
+
+ throw new MqttProtocolViolationException("Boolean values can be 0 or 1 only.");
+ }
+
+ public byte[] ReadRemainingData()
+ {
+ return new ArraySegment(_buffer, (int)Offset, (int)(_length - Offset)).ToArray();
+ }
+
+ public ushort ReadTwoByteInteger()
+ {
+ ValidateReceiveBuffer(2);
+
+ var msb = _buffer[Offset++];
+ var lsb = _buffer[Offset++];
+
+ return (ushort)(msb << 8 | lsb);
+ }
+
+ public uint ReadFourByteInteger()
+ {
+ ValidateReceiveBuffer(4);
+
+ var byte0 = _buffer[Offset++];
+ var byte1 = _buffer[Offset++];
+ var byte2 = _buffer[Offset++];
+ var byte3 = _buffer[Offset++];
+
+ return (uint)(byte0 << 24 | byte1 << 16 | byte2 << 8 | byte3);
+ }
+
+ public byte[] ReadWithLengthPrefix()
+ {
+ return ReadSegmentWithLengthPrefix().ToArray();
+ }
+
+ private ArraySegment ReadSegmentWithLengthPrefix()
+ {
+ var length = ReadTwoByteInteger();
+
+ ValidateReceiveBuffer(length);
+
+ var result = new ArraySegment(_buffer, (int)Offset, length);
+ Offset += length;
+
+ return result;
+ }
+
+ public uint ReadVariableLengthInteger()
+ {
+ var multiplier = 1;
+ var value = 0U;
+ byte encodedByte;
+
+ do
+ {
+ encodedByte = ReadByte();
+ value += (uint)((encodedByte & 127) * multiplier);
+
+ if (multiplier > 2097152)
+ {
+ throw new MqttProtocolViolationException("Variable length integer is invalid.");
+ }
+
+ multiplier *= 128;
+ } while ((encodedByte & 128) != 0);
+
+ return value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ValidateReceiveBuffer(uint length)
+ {
+ if (_length < Offset + length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(_buffer), $"Expected at least {Offset + length} bytes but there are only {_length} bytes");
+ }
+ }
+
+ public string ReadStringWithLengthPrefix()
+ {
+ var buffer = ReadSegmentWithLengthPrefix();
+ return Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
+ }
+ }
+}
diff --git a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs
new file mode 100644
index 00000000..723b6b8c
--- /dev/null
+++ b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs
@@ -0,0 +1,166 @@
+using System;
+using MQTTnet.Adapter;
+using MQTTnet.Exceptions;
+using MQTTnet.Formatter.V3;
+using MQTTnet.Formatter.V5;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Formatter
+{
+ public class MqttPacketFormatterAdapter
+ {
+ private IMqttPacketFormatter _formatter;
+
+ public MqttPacketFormatterAdapter()
+ : this(new MqttPacketWriter())
+ {
+ }
+
+ public MqttPacketFormatterAdapter(MqttProtocolVersion protocolVersion)
+ : this(protocolVersion, new MqttPacketWriter())
+ {
+ }
+
+ public MqttPacketFormatterAdapter(MqttProtocolVersion protocolVersion, IMqttPacketWriter writer)
+ : this(writer)
+ {
+ UseProtocolVersion(protocolVersion);
+ }
+
+ public MqttPacketFormatterAdapter(IMqttPacketWriter writer)
+ {
+ Writer = writer;
+ }
+
+ public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown;
+
+ public IMqttDataConverter DataConverter
+ {
+ get
+ {
+ ThrowIfFormatterNotSet();
+
+ return _formatter.DataConverter;
+ }
+ }
+
+ public IMqttPacketWriter Writer { get; }
+
+ public ArraySegment Encode(MqttBasePacket packet)
+ {
+ if (packet == null) throw new ArgumentNullException(nameof(packet));
+
+ ThrowIfFormatterNotSet();
+
+ return _formatter.Encode(packet);
+ }
+
+ public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket)
+ {
+ if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
+
+ ThrowIfFormatterNotSet();
+
+ return _formatter.Decode(receivedMqttPacket);
+ }
+
+ public void FreeBuffer()
+ {
+ _formatter?.FreeBuffer();
+ }
+
+ public void DetectProtocolVersion(ReceivedMqttPacket receivedMqttPacket)
+ {
+ var protocolVersion = ParseProtocolVersion(receivedMqttPacket);
+
+ // Reset the position of the stream because the protocol version is part of
+ // the regular CONNECT packet. So it will not properly deserialized if this
+ // data is missing.
+ receivedMqttPacket.Body.Seek(0);
+
+ UseProtocolVersion(protocolVersion);
+ }
+
+ private void UseProtocolVersion(MqttProtocolVersion protocolVersion)
+ {
+ if (protocolVersion == MqttProtocolVersion.Unknown)
+ {
+ throw new InvalidOperationException("MQTT protocol version is invalid.");
+ }
+
+ ProtocolVersion = protocolVersion;
+ _formatter = GetMqttPacketFormatter(protocolVersion, Writer);
+ }
+
+ public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, IMqttPacketWriter writer)
+ {
+ if (protocolVersion == MqttProtocolVersion.Unknown)
+ {
+ throw new InvalidOperationException("MQTT protocol version is invalid.");
+ }
+
+ switch (protocolVersion)
+ {
+ case MqttProtocolVersion.V500:
+ {
+ return new MqttV500PacketFormatter(writer);
+ }
+ case MqttProtocolVersion.V311:
+ {
+ return new MqttV311PacketFormatter(writer);
+ }
+ case MqttProtocolVersion.V310:
+ {
+ return new MqttV310PacketFormatter(writer);
+ }
+ default:
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+
+ private MqttProtocolVersion ParseProtocolVersion(ReceivedMqttPacket receivedMqttPacket)
+ {
+ if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
+
+ var protocolName = receivedMqttPacket.Body.ReadStringWithLengthPrefix();
+ var protocolLevel = receivedMqttPacket.Body.ReadByte();
+
+ if (protocolName == "MQTT")
+ {
+ if (protocolLevel == 5)
+ {
+ return MqttProtocolVersion.V500;
+ }
+
+ if (protocolLevel == 4)
+ {
+ return MqttProtocolVersion.V311;
+ }
+
+ throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported.");
+ }
+
+ if (protocolName == "MQIsdp")
+ {
+ if (protocolLevel == 3)
+ {
+ return MqttProtocolVersion.V310;
+ }
+
+ throw new MqttProtocolViolationException($"Protocol level '{protocolLevel}' not supported.");
+ }
+
+ throw new MqttProtocolViolationException($"Protocol '{protocolName}' not supported.");
+ }
+
+ private void ThrowIfFormatterNotSet()
+ {
+ if (_formatter == null)
+ {
+ throw new InvalidOperationException("Protocol version not set or detected.");
+ }
+ }
+ }
+}
diff --git a/Source/MQTTnet/Formatter/MqttPacketReader.cs b/Source/MQTTnet/Formatter/MqttPacketReader.cs
new file mode 100644
index 00000000..2589c5b2
--- /dev/null
+++ b/Source/MQTTnet/Formatter/MqttPacketReader.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Channel;
+using MQTTnet.Exceptions;
+
+namespace MQTTnet.Formatter
+{
+ public class MqttPacketReader
+ {
+ private readonly byte[] _singleByteBuffer = new byte[1];
+
+ private readonly IMqttChannel _channel;
+
+ public MqttPacketReader(IMqttChannel channel)
+ {
+ _channel = channel ?? throw new ArgumentNullException(nameof(channel));
+ }
+
+ public async Task