mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a26b641284 | ||
|
|
7be3685850 | ||
|
|
1a6be47144 | ||
|
|
5d73de2a86 | ||
|
|
2eaebe0e6a | ||
|
|
d7b552fce2 | ||
|
|
bea6d75251 | ||
|
|
f7db00c08e | ||
|
|
f259bbcc9b | ||
|
|
252772007b | ||
|
|
21a9974e5d | ||
|
|
d63143bdc9 | ||
|
|
aad9eac361 | ||
|
|
e8a683b936 | ||
|
|
e475e36ce7 | ||
|
|
611c230da2 | ||
|
|
18839de083 | ||
|
|
194664bc9c | ||
|
|
10a33fd153 | ||
|
|
868aa7d89a | ||
|
|
1f8792cf87 | ||
|
|
7bb99c8b37 | ||
|
|
7f5504b01b | ||
|
|
fd789417e7 | ||
|
|
f1f9924bce | ||
|
|
b0c603082b | ||
|
|
f3f10e9f65 | ||
|
|
beb228cc05 | ||
|
|
7a7f0640ba | ||
|
|
55d009ce6b | ||
|
|
e23ac4f703 | ||
|
|
fc367ddb64 | ||
|
|
cf217034fa | ||
|
|
6b6627f823 | ||
|
|
4f51a238b3 | ||
|
|
733c587e45 | ||
|
|
3260920828 | ||
|
|
4e6aeb91a4 | ||
|
|
5056843611 | ||
|
|
a65c015a58 | ||
|
|
e63f26789f | ||
|
|
9a598892a7 | ||
|
|
857ab005be | ||
|
|
33e4ec1943 | ||
|
|
6a564a0c72 | ||
|
|
3a4dce5080 | ||
|
|
4e6118d324 | ||
|
|
269bfa09ac | ||
|
|
59dd6b98a3 | ||
|
|
9f37306f20 | ||
|
|
85b000c6b7 | ||
|
|
5a919ce5d2 | ||
|
|
57ab8e5abc | ||
|
|
8f6d48be81 | ||
|
|
38cfa15cc3 | ||
|
|
6adf7096ec | ||
|
|
df149eb0b3 | ||
|
|
63c9329190 | ||
|
|
246a10649a | ||
|
|
0a76ac15c6 | ||
|
|
d801af5616 | ||
|
|
257211fc7a | ||
|
|
a407fc381b | ||
|
|
4fa275bf2b | ||
|
|
7cf12dc751 | ||
|
|
40aefd0ad3 | ||
|
|
c20142671f | ||
|
|
75dfe68a94 | ||
|
|
92518fc4ec | ||
|
|
16180e83ba | ||
|
|
c779b394f1 | ||
|
|
e60a732b61 | ||
|
|
7ea9415c63 | ||
|
|
13c399bc9a | ||
|
|
4565c3990a | ||
|
|
2bd2648b48 | ||
|
|
1b56155f1c | ||
|
|
626930cfae | ||
|
|
6cf9cb0af2 | ||
|
|
60941c04e5 | ||
|
|
ba68b1274b | ||
|
|
dbe12b9c48 | ||
|
|
52b79a140b | ||
|
|
c270225e9e | ||
|
|
3f5d4e9825 | ||
|
|
e80d19ba9b | ||
|
|
48837529a2 | ||
|
|
43e3ad4c00 | ||
|
|
08c9ecb011 | ||
|
|
fc3965f5a3 | ||
|
|
1d553c5ece | ||
|
|
07ad9fc557 | ||
|
|
88e8691ce0 | ||
|
|
28641892f5 |
@@ -13,7 +13,7 @@
|
||||
<description>This is a support library to integrate MQTTnet into AspNetCore.</description>
|
||||
<releaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2020</copyright>
|
||||
<tags>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</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="netstandard2.0">
|
||||
<dependency id="MQTTnet" version="$nugetVersion" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</description>
|
||||
<releaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2020</copyright>
|
||||
<tags>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</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags>
|
||||
<dependencies>
|
||||
<dependency id="MQTTnet" version="$nugetVersion" />
|
||||
</dependencies>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
|
||||
<releaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2020</copyright>
|
||||
<tags>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</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags>
|
||||
<dependencies>
|
||||
<dependency id="MQTTnet" version="$nugetVersion" />
|
||||
</dependencies>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>This is an extension library which allows using _WebSocket4Net_ as transport for MQTTnet clients.</description>
|
||||
<releaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2020</copyright>
|
||||
<tags>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</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="netstandard1.3">
|
||||
<dependency id="MQTTnet" version="$nugetVersion" />
|
||||
|
||||
+17
-17
@@ -12,25 +12,16 @@
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</description>
|
||||
<releaseNotes>
|
||||
* [Core] Improved logger performance (BREAKING CHANGE!).
|
||||
* [Core] Renamed some topic filter relevant classes (BREAKING CHANGE!).
|
||||
* [Core] Improved task management for UWP connections (thanks to @xgstation).
|
||||
* [Core] Fixed broken logger which decreases overall performance.
|
||||
* [Core] Fixed issue in closed socket detection (fixes high CPU load issue).
|
||||
* [Client] Added method to trigger PING/PONG manually (connection check etc.).
|
||||
* [Client] Added support for certificate validation callback when using Web Sockets (requires netstandard2.1+).
|
||||
* [Client] Fixed a memory leak when web socket based connections trying to reconnect with an offline server.
|
||||
* [Client] Fixed a memory leak when TCP based connections trying to reconnect with an offline server.
|
||||
* [Client] Fixed an issue when connecting to an invalid host (format).
|
||||
* [Client] Added support for user properties in CONNECT packet.
|
||||
* [Client] Removed _KeepAliveSendInterval_ and improved keep alive handling (BREAKING CHANGE!).
|
||||
* [ManagedClient] Added method to trigger PING/PONG manually (connection check etc.).
|
||||
* [MQTTnet.AspNetCore] improved compatibility with AspNetCore 3.1.
|
||||
* [MQTTnet.AspNetCore] Fixed several packaging issues with the Nuget package.
|
||||
* [MQTTnet.Server] Fixed wrong version output.
|
||||
* [Client] Fixed wrong value for "ClientWasConnected" in "MqttClientDisconnectedEventArgs" #976 (thanks to @dbeinder).
|
||||
* [Client] Added direct support for Amazon AWS connections (requires .NET Core 3.1) (thanks to @henning-krause).
|
||||
* [Client] handle disconnect package and propagate disconnect reason code (thanks to @JanEggers)
|
||||
* [ManagedClient] Exposed the internal MQTT client.
|
||||
* [Server] Added client message queue interceptor for QoS level (thanks to @msallin).
|
||||
* [Server] improved behavior when multiple connections fight over a session (thanks to @JanEggers)
|
||||
* [Server] Exposed _ClientDisconnectedInterceptor_ (thanks to @SeppPenner).
|
||||
</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2020</copyright>
|
||||
<tags>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</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="netstandard1.3">
|
||||
<dependency id="NETStandard.Library" version="1.3.0" />
|
||||
@@ -50,6 +41,12 @@
|
||||
<dependency id="System.Net.WebSockets" version="4.3.0" />
|
||||
<dependency id="System.Net.WebSockets.Client" version="4.3.2" />
|
||||
</group>
|
||||
<group targetFramework="netcoreapp3.1">
|
||||
<dependency id="NETStandard.Library" version="2.0.0" />
|
||||
<dependency id="System.Net.Security" version="4.3.2" />
|
||||
<dependency id="System.Net.WebSockets" version="4.3.0" />
|
||||
<dependency id="System.Net.WebSockets.Client" version="4.3.2" />
|
||||
</group>
|
||||
<group targetFramework="uap10.0">
|
||||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.2.10" />
|
||||
</group>
|
||||
@@ -72,6 +69,9 @@
|
||||
<!-- .NET Standard 2.1 -->
|
||||
<file src="..\Source\MQTTnet\bin\Release\netstandard2.1\MQTTnet.*" target="lib\netstandard2.1\"/>
|
||||
|
||||
<!-- .NET Standard 3.1 -->
|
||||
<file src="..\Source\MQTTnet\bin\Release\netcoreapp3.1\MQTTnet.*" target="lib\netcoreapp3.1\"/>
|
||||
|
||||
<!-- Universal Windows -->
|
||||
<file src="..\Source\MQTTnet\bin\Release\uap10.0\MQTTnet.*" target="lib\uap10.0\"/>
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ vstest.console.exe ..\Tests\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\M
|
||||
&$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="netstandard2.1" /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="netcoreapp3.1" /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
|
||||
|
||||
+60
@@ -55,6 +55,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocke
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{A56E3128-1639-4F31-873A-325E14BB6295}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{61B165A0-5AA8-4E04-A53D-A22A84AA6EB7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Test.BlazorApp.Server", "Tests\MQTTnet.Test.BlazorApp\Server\MQTTnet.Test.BlazorApp.Server.csproj", "{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Test.BlazorApp.Client", "Tests\MQTTnet.Test.BlazorApp\Client\MQTTnet.Test.BlazorApp.Client.csproj", "{D260D63D-7902-4C55-9665-84C5CACBBB24}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Test.BlazorApp.Shared", "Tests\MQTTnet.Test.BlazorApp\Shared\MQTTnet.Test.BlazorApp.Shared.csproj", "{DDB069BA-6E1A-48C7-B374-5D903B553CAD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -266,6 +274,54 @@ Global
|
||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -283,6 +339,10 @@ Global
|
||||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B} = {A56E3128-1639-4F31-873A-325E14BB6295}
|
||||
{2BD01D53-4CA5-4142-BE8D-313876395E3E} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587}
|
||||
{61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7}
|
||||
{D260D63D-7902-4C55-9665-84C5CACBBB24} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7}
|
||||
{DDB069BA-6E1A-48C7-B374-5D903B553CAD} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||
|
||||
@@ -72,6 +72,7 @@ _MQTTnet Server_ is a standalone cross platform MQTT server (like mosquitto) bas
|
||||
* Universal Windows Platform (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU, Windows 10 IoT Core)
|
||||
* Xamarin.Android 7.5+
|
||||
* Xamarin.iOS 10.14+
|
||||
* Blazor WebAssembly 3.2.0
|
||||
|
||||
## Supported MQTT versions
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore.Extensions
|
||||
{
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore.Extensions
|
||||
{
|
||||
public static class ConnectionBuilderExtensions
|
||||
{
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
#if NETCOREAPP3_1
|
||||
using System;
|
||||
#endif
|
||||
|
||||
namespace MQTTnet.AspNetCore.Extensions
|
||||
{
|
||||
public static class ConnectionRouteBuilderExtensions
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore.Extensions
|
||||
{
|
||||
public static class ReaderExtensions
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
namespace MQTTnet.AspNetCore.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
|
||||
@@ -8,8 +8,15 @@
|
||||
<PackageId />
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
@@ -19,7 +26,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,11 +10,19 @@ using System.IO.Pipelines;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.AspNetCore.Extensions;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class MqttConnectionContext : IMqttChannelAdapter
|
||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
{
|
||||
readonly AsyncLock _writerLock = new AsyncLock();
|
||||
readonly SpanBasedMqttPacketBodyReader _reader;
|
||||
|
||||
PipeReader _input;
|
||||
PipeWriter _output;
|
||||
|
||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||
{
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
@@ -29,10 +37,6 @@ namespace MQTTnet.AspNetCore
|
||||
_reader = new SpanBasedMqttPacketBodyReader();
|
||||
}
|
||||
|
||||
private PipeReader _input;
|
||||
private PipeWriter _output;
|
||||
private readonly SpanBasedMqttPacketBodyReader _reader;
|
||||
|
||||
public string Endpoint
|
||||
{
|
||||
get
|
||||
@@ -57,9 +61,8 @@ namespace MQTTnet.AspNetCore
|
||||
|
||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
||||
|
||||
private IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
||||
|
||||
public ConnectionContext Connection { get; }
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public long BytesSent { get; set; }
|
||||
@@ -68,7 +71,7 @@ namespace MQTTnet.AspNetCore
|
||||
public Action ReadingPacketStartedCallback { get; set; }
|
||||
public Action ReadingPacketCompletedCallback { get; set; }
|
||||
|
||||
private readonly SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1);
|
||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
||||
|
||||
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -142,6 +145,13 @@ namespace MQTTnet.AspNetCore
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// completing the cannels makes sure that there is no more data read after a protocol error
|
||||
_input?.Complete(e);
|
||||
_output?.Complete(e);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReadingPacketCompletedCallback?.Invoke();
|
||||
@@ -160,25 +170,18 @@ namespace MQTTnet.AspNetCore
|
||||
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
var formatter = PacketFormatterAdapter;
|
||||
var buffer = formatter.Encode(packet);
|
||||
var msg = buffer.AsMemory();
|
||||
var output = _output;
|
||||
|
||||
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var buffer = formatter.Encode(packet);
|
||||
var msg = buffer.AsMemory();
|
||||
var output = _output;
|
||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
BytesSent += msg.Length;
|
||||
}
|
||||
|
||||
PacketFormatterAdapter.FreeBuffer();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writerSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class MqttSubProtocolSelector
|
||||
{
|
||||
public static string SelectSubProtocol(HttpRequest request)
|
||||
{
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
string subProtocol = null;
|
||||
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
{
|
||||
subProtocol = SelectSubProtocol(requestedSubProtocolValues);
|
||||
}
|
||||
|
||||
return subProtocol;
|
||||
}
|
||||
|
||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||
{
|
||||
if (requestedSubProtocolValues == null) throw new ArgumentNullException(nameof(requestedSubProtocolValues));
|
||||
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
return requestedSubProtocolValues
|
||||
.OrderByDescending(p => p.Length)
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class SpanBasedMqttPacketBodyReader : IMqttPacketBodyReader
|
||||
{
|
||||
private ReadOnlyMemory<byte> _buffer;
|
||||
ReadOnlyMemory<byte> _buffer;
|
||||
|
||||
private int _offset;
|
||||
int _offset;
|
||||
|
||||
public void SetBuffer(ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
@@ -38,16 +38,6 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
return ReadSegmentWithLengthPrefix().ToArray();
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> 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()
|
||||
{
|
||||
@@ -121,5 +111,19 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
_offset = position;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> ReadSegmentWithLengthPrefix()
|
||||
{
|
||||
var span = _buffer.Span;
|
||||
var length = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(_offset));
|
||||
|
||||
if (Length < _offset + length)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Expected at least {_offset + 2 + length} bytes but there are only {Length} bytes");
|
||||
}
|
||||
var result = span.Slice(_offset + 2, length);
|
||||
_offset += 2 + length;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,15 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public class SpanBasedMqttPacketWriter : IMqttPacketWriter
|
||||
{
|
||||
private readonly ArrayPool<byte> _pool;
|
||||
readonly ArrayPool<byte> _pool = ArrayPool<byte>.Create();
|
||||
|
||||
public SpanBasedMqttPacketWriter()
|
||||
{
|
||||
_pool = ArrayPool<byte>.Create();
|
||||
|
||||
Reset(0);
|
||||
}
|
||||
|
||||
private byte[] _buffer;
|
||||
private int _position;
|
||||
byte[] _buffer;
|
||||
int _position;
|
||||
|
||||
public int Length { get; set; }
|
||||
|
||||
@@ -112,7 +111,7 @@ namespace MQTTnet.AspNetCore
|
||||
Commit(payload.Length);
|
||||
}
|
||||
|
||||
private void Commit(int count)
|
||||
void Commit(int count)
|
||||
{
|
||||
if (_position == Length)
|
||||
{
|
||||
@@ -122,7 +121,7 @@ namespace MQTTnet.AspNetCore
|
||||
_position += count;
|
||||
}
|
||||
|
||||
private void GrowIfNeeded(int requiredAdditional)
|
||||
void GrowIfNeeded(int requiredAdditional)
|
||||
{
|
||||
var requiredTotal = _position + requiredAdditional;
|
||||
if (_buffer.Length >= requiredTotal)
|
||||
|
||||
@@ -4,30 +4,46 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
|
||||
{
|
||||
bool IsStarted { get; }
|
||||
bool IsConnected { get; }
|
||||
int PendingApplicationMessagesCount { get; }
|
||||
IManagedMqttClientOptions Options { get; }
|
||||
/// <summary>
|
||||
/// Gets the internally used MQTT client.
|
||||
/// This property should be used with caution because manipulating the internal client might break the managed client.
|
||||
/// </summary>
|
||||
IMqttClient InternalClient { get; }
|
||||
|
||||
bool IsStarted { get; }
|
||||
|
||||
bool IsConnected { get; }
|
||||
|
||||
int PendingApplicationMessagesCount { get; }
|
||||
|
||||
IManagedMqttClientOptions Options { get; }
|
||||
|
||||
IMqttClientConnectedHandler ConnectedHandler { get; set; }
|
||||
|
||||
IMqttClientDisconnectedHandler DisconnectedHandler { get; set; }
|
||||
|
||||
IApplicationMessageProcessedHandler ApplicationMessageProcessedHandler { get; set; }
|
||||
|
||||
IApplicationMessageSkippedHandler ApplicationMessageSkippedHandler { get; set; }
|
||||
|
||||
IConnectingFailedHandler ConnectingFailedHandler { get; set; }
|
||||
|
||||
ISynchronizingSubscriptionsFailedHandler SynchronizingSubscriptionsFailedHandler { get; set; }
|
||||
|
||||
Task StartAsync(IManagedMqttClientOptions options);
|
||||
|
||||
Task StopAsync();
|
||||
|
||||
Task PingAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters);
|
||||
|
||||
Task UnsubscribeAsync(IEnumerable<string> topics);
|
||||
|
||||
Task PublishAsync(ManagedMqttApplicationMessage applicationMessages);
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
IMqttClientOptions ClientOptions { get; }
|
||||
|
||||
bool AutoReconnect { get; }
|
||||
|
||||
TimeSpan AutoReconnectDelay { get; }
|
||||
|
||||
TimeSpan ConnectionCheckInterval { get; }
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
@@ -23,6 +26,10 @@
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClient : Disposable, IManagedMqttClient
|
||||
{
|
||||
private readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
|
||||
readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// The subscriptions are managed in 2 separate buckets:
|
||||
@@ -27,33 +27,39 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
/// any thread and are therefore mutex'ed. <see cref="_reconnectSubscriptions"/> get sent to the broker
|
||||
/// at reconnect and are solely owned by <see cref="MaintainConnectionAsync"/>.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly HashSet<string> _unsubscriptions = new HashSet<string>();
|
||||
private readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
|
||||
readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
|
||||
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
|
||||
|
||||
private readonly IMqttClient _mqttClient;
|
||||
private readonly IMqttNetScopedLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
|
||||
private readonly AsyncLock _messageQueueLock = new AsyncLock();
|
||||
readonly AsyncLock _messageQueueLock = new AsyncLock();
|
||||
|
||||
private CancellationTokenSource _connectionCancellationToken;
|
||||
private CancellationTokenSource _publishingCancellationToken;
|
||||
private Task _maintainConnectionTask;
|
||||
CancellationTokenSource _connectionCancellationToken;
|
||||
CancellationTokenSource _publishingCancellationToken;
|
||||
Task _maintainConnectionTask;
|
||||
|
||||
private ManagedMqttClientStorageManager _storageManager;
|
||||
ManagedMqttClientStorageManager _storageManager;
|
||||
|
||||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
InternalClient = mqttClient;
|
||||
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
_logger = logger.CreateScopedLogger(nameof(ManagedMqttClient));
|
||||
}
|
||||
|
||||
public bool IsConnected => _mqttClient.IsConnected;
|
||||
|
||||
public bool IsStarted => _connectionCancellationToken != null;
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public int PendingApplicationMessagesCount => _messageQueue.Count;
|
||||
|
||||
public IManagedMqttClientOptions Options { get; private set; }
|
||||
|
||||
public IMqttClientConnectedHandler ConnectedHandler
|
||||
@@ -88,8 +94,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
|
||||
|
||||
|
||||
|
||||
if (!_maintainConnectionTask?.IsCompleted ?? false) throw new InvalidOperationException("The managed client is already started.");
|
||||
|
||||
Options = options;
|
||||
@@ -105,11 +110,16 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
_connectionCancellationToken = new CancellationTokenSource();
|
||||
|
||||
_maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
|
||||
_maintainConnectionTask.Forget(_logger);
|
||||
if (Options.AutoReconnect)
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancellationTokenSource.Token;
|
||||
_connectionCancellationToken = cancellationTokenSource;
|
||||
|
||||
_maintainConnectionTask = Task.Run(() => MaintainConnectionAsync(cancellationToken), cancellationToken);
|
||||
_maintainConnectionTask.Forget(_logger);
|
||||
}
|
||||
|
||||
_logger.Info("Started");
|
||||
}
|
||||
|
||||
@@ -260,10 +270,11 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
_mqttClient.Dispose();
|
||||
_subscriptionsQueuedSignal.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
|
||||
async Task MaintainConnectionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -303,7 +314,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
|
||||
async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -346,7 +357,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
|
||||
async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -384,7 +395,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
|
||||
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
|
||||
{
|
||||
Exception transmitException = null;
|
||||
try
|
||||
@@ -449,7 +460,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
async Task PublishSubscriptionsAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
var endTime = DateTime.UtcNow + timeout;
|
||||
while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
|
||||
@@ -501,7 +512,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PublishReconnectSubscriptionsAsync()
|
||||
async Task PublishReconnectSubscriptionsAsync()
|
||||
{
|
||||
_logger.Info("Publishing subscriptions at reconnect");
|
||||
|
||||
@@ -519,7 +530,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSubscriptionExceptionAsync(Exception exception)
|
||||
async Task HandleSubscriptionExceptionAsync(Exception exception)
|
||||
{
|
||||
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
||||
|
||||
@@ -530,7 +541,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ReconnectionResult> ReconnectIfRequiredAsync()
|
||||
async Task<ReconnectionResult> ReconnectIfRequiredAsync()
|
||||
{
|
||||
if (_mqttClient.IsConnected)
|
||||
{
|
||||
@@ -554,34 +565,47 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
}
|
||||
|
||||
private void StartPublishing()
|
||||
void StartPublishing()
|
||||
{
|
||||
if (_publishingCancellationToken != null)
|
||||
{
|
||||
StopPublishing();
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
_publishingCancellationToken = cts;
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = cancellationTokenSource.Token;
|
||||
_publishingCancellationToken = cancellationTokenSource;
|
||||
|
||||
Task.Run(() => PublishQueuedMessagesAsync(cts.Token), cts.Token).Forget(_logger);
|
||||
Task.Run(() => PublishQueuedMessagesAsync(cancellationToken), cancellationToken).Forget(_logger);
|
||||
}
|
||||
|
||||
private void StopPublishing()
|
||||
void StopPublishing()
|
||||
{
|
||||
_publishingCancellationToken?.Cancel(false);
|
||||
_publishingCancellationToken?.Dispose();
|
||||
_publishingCancellationToken = null;
|
||||
try
|
||||
{
|
||||
_publishingCancellationToken?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_publishingCancellationToken?.Dispose();
|
||||
_publishingCancellationToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopMaintainingConnection()
|
||||
void StopMaintainingConnection()
|
||||
{
|
||||
_connectionCancellationToken?.Cancel(false);
|
||||
_connectionCancellationToken?.Dispose();
|
||||
_connectionCancellationToken = null;
|
||||
try
|
||||
{
|
||||
_connectionCancellationToken?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_connectionCancellationToken?.Dispose();
|
||||
_connectionCancellationToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan GetRemainingTime(DateTime endTime)
|
||||
static TimeSpan GetRemainingTime(DateTime endTime)
|
||||
{
|
||||
var remainingTime = endTime - DateTime.UtcNow;
|
||||
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
|
||||
|
||||
@@ -76,36 +76,39 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return client;
|
||||
}
|
||||
|
||||
public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, Func<MqttApplicationMessageReceivedEventArgs, Task> handler)
|
||||
public static TReceiver UseApplicationMessageReceivedHandler<TReceiver>(this TReceiver receiver, Func<MqttApplicationMessageReceivedEventArgs, Task> handler)
|
||||
where TReceiver : IApplicationMessageReceiver
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (receiver == null) throw new ArgumentNullException(nameof(receiver));
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
return client.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
|
||||
return receiver.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
|
||||
}
|
||||
|
||||
return client.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
|
||||
return receiver.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
|
||||
}
|
||||
|
||||
public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, Action<MqttApplicationMessageReceivedEventArgs> handler)
|
||||
public static TReceiver UseApplicationMessageReceivedHandler<TReceiver>(this TReceiver receiver, Action<MqttApplicationMessageReceivedEventArgs> handler)
|
||||
where TReceiver : IApplicationMessageReceiver
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (receiver == null) throw new ArgumentNullException(nameof(receiver));
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
return client.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
|
||||
return receiver.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
|
||||
}
|
||||
|
||||
return client.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
|
||||
return receiver.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
|
||||
}
|
||||
|
||||
public static IManagedMqttClient UseApplicationMessageReceivedHandler(this IManagedMqttClient client, IMqttApplicationMessageReceivedHandler handler)
|
||||
public static TReceiver UseApplicationMessageReceivedHandler<TReceiver>(this TReceiver receiver, IMqttApplicationMessageReceivedHandler handler)
|
||||
where TReceiver : IApplicationMessageReceiver
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (receiver == null) throw new ArgumentNullException(nameof(receiver));
|
||||
|
||||
client.ApplicationMessageReceivedHandler = handler;
|
||||
return client;
|
||||
receiver.ApplicationMessageReceivedHandler = handler;
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(this IManagedMqttClient client, params MqttTopicFilter[] topicFilters)
|
||||
@@ -138,92 +141,92 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return client.UnsubscribeAsync(topicFilters);
|
||||
}
|
||||
|
||||
public static async Task PublishAsync(this IManagedMqttClient client, IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
await client.PublishAsync(applicationMessage).ConfigureAwait(false);
|
||||
await publisher.PublishAsync(applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, MqttApplicationMessage applicationMessage)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
return client.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
return publisher.PublishAsync(applicationMessage, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task PublishAsync(this IManagedMqttClient client, params MqttApplicationMessage[] applicationMessages)
|
||||
public static async Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
await client.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
|
||||
await publisher.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return client.PublishAsync(builder => builder
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic));
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic, string payload)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return client.PublishAsync(builder => builder
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload));
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return client.PublishAsync(builder => builder
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel));
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return client.PublishAsync(builder => builder
|
||||
return publisher.PublishAsync(builder => builder
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithRetainFlag(retain));
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder, CancellationToken cancellationToken)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder, CancellationToken cancellationToken)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
|
||||
var message = builder(new MqttApplicationMessageBuilder()).Build();
|
||||
return client.PublishAsync(message, cancellationToken);
|
||||
return publisher.PublishAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IManagedMqttClient client, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
|
||||
public static Task<MqttClientPublishResult> PublishAsync(this IApplicationMessagePublisher publisher, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
|
||||
|
||||
var message = builder(new MqttApplicationMessageBuilder()).Build();
|
||||
return client.PublishAsync(message, CancellationToken.None);
|
||||
return publisher.PublishAsync(message, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Client.Options;
|
||||
using MQTTnet.Server;
|
||||
|
||||
@@ -9,6 +8,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public IMqttClientOptions ClientOptions { get; set; }
|
||||
|
||||
public bool AutoReconnect { get; set; } = true;
|
||||
|
||||
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Client.Options;
|
||||
using MQTTnet.Server;
|
||||
|
||||
@@ -7,8 +6,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientOptionsBuilder
|
||||
{
|
||||
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||
private MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||
MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithMaxPendingMessages(int value)
|
||||
{
|
||||
@@ -70,6 +69,18 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithAutoReconnect()
|
||||
{
|
||||
_options.AutoReconnect = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptionsBuilder WithoutAutoReconnect()
|
||||
{
|
||||
_options.AutoReconnect = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions Build()
|
||||
{
|
||||
if (_clientOptionsBuilder != null)
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
@@ -23,6 +26,10 @@
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
using MQTTnet.Extensions.Rpc.Options.TopicGeneration;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc.Options
|
||||
namespace MQTTnet.Extensions.Rpc.Options.TopicGeneration
|
||||
{
|
||||
public class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||
{
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<Company />
|
||||
<Authors />
|
||||
<PackageId />
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
@@ -23,6 +26,10 @@
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "3.1.4",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,22 @@
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<Company />
|
||||
<Product>MQTTnet</Product>
|
||||
<Description />
|
||||
<Authors />
|
||||
<Description>Standalone MQTT server with Python scripting backend.</Description>
|
||||
<Authors>The authors of MQTTnet.Server.</Authors>
|
||||
<PackageId />
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>1.0.0</Version>
|
||||
<Version>3.0.11</Version>
|
||||
<TypeScriptToolsVersion>3.3</TypeScriptToolsVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<StartupObject>MQTTnet.Server.Program</StartupObject>
|
||||
<UserSecretsId>c564f0de-28b4-45bf-b726-4d665d705653</UserSecretsId>
|
||||
<Copyright>(c) Christian Kratky 2016-2020</Copyright>
|
||||
<PackageProjectUrl>https://github.com/chkr1011/MQTTnet</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/chkr1011/MQTTnet</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@@ -29,13 +32,6 @@
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="wwwroot\**" />
|
||||
<Content Remove="wwwroot\**" />
|
||||
<EmbeddedResource Remove="wwwroot\**" />
|
||||
<None Remove="wwwroot\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
@@ -43,12 +39,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IronPython" Version="2.7.10-candidate1" />
|
||||
<PackageReference Include="IronPython.StdLib" Version="2.7.10-candidate1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.3.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.3.3" />
|
||||
<PackageReference Include="IronPython" Version="2.7.10" />
|
||||
<PackageReference Include="IronPython.StdLib" Version="2.7.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.5.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -72,11 +68,19 @@
|
||||
<None Update="Web\authorization_handler.py">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Web\wwwroot\Hello.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Web\wwwroot\mqtt_Test.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Web\wwwroot\pahoJS_Test.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\PublishProfiles\" />
|
||||
<Folder Include="Web\wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Server.Web;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
@@ -21,8 +22,8 @@ namespace MQTTnet.Server
|
||||
webBuilder.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
})
|
||||
.UseWebRoot(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "wwwroot"))
|
||||
.UseStartup<Startup>();
|
||||
|
||||
}).Build().Run();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.Scripting.Utils;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Server.Configuration;
|
||||
using MQTTnet.Server.Logging;
|
||||
using MQTTnet.Server.Mqtt;
|
||||
@@ -40,6 +41,9 @@ namespace MQTTnet.Server.Web
|
||||
DataSharingService dataSharingService,
|
||||
MqttSettingsModel mqttSettings)
|
||||
{
|
||||
application.UseDefaultFiles();
|
||||
application.UseStaticFiles();
|
||||
|
||||
application.UseHsts();
|
||||
application.UseRouting();
|
||||
application.UseCors(x => x
|
||||
@@ -49,9 +53,7 @@ namespace MQTTnet.Server.Web
|
||||
|
||||
application.UseAuthentication();
|
||||
application.UseAuthorization();
|
||||
|
||||
application.UseStaticFiles();
|
||||
|
||||
|
||||
application.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
@@ -81,7 +83,6 @@ namespace MQTTnet.Server.Web
|
||||
{
|
||||
services.AddCors();
|
||||
|
||||
|
||||
services.AddControllers();
|
||||
|
||||
services.AddMvc()
|
||||
@@ -197,7 +198,13 @@ namespace MQTTnet.Server.Web
|
||||
{
|
||||
if (context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false))
|
||||
string subProtocol = null;
|
||||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
{
|
||||
subProtocol = MqttSubProtocolSelector.SelectSubProtocol(requestedSubProtocolValues);
|
||||
}
|
||||
|
||||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false))
|
||||
{
|
||||
await mqttServerService.RunWebSocketConnectionAsync(webSocket, context).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
World
|
||||
@@ -0,0 +1,33 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>MQTT JS Example</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/mqtt@4.0.1/dist/mqtt.min.js" type="text/javascript"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var client = mqtt.connect('ws://localhost:80/mqtt')
|
||||
|
||||
client.on('connect', function () {
|
||||
client.subscribe('mqtt_subscription', function (err) {
|
||||
console.log("Subscribed");
|
||||
})
|
||||
})
|
||||
|
||||
client.on('message', function (topic, message) {
|
||||
console.log(topic);
|
||||
console.log(message.toString())
|
||||
})
|
||||
|
||||
function buttonClick() {
|
||||
client.publish("MQTT_JS_TOPIC", "payload");
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button onclick="buttonClick()">Publish</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>paho JS Example</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var mqttClient = new Paho.MQTT.Client("localhost", 80, "/mqtt", "pahoJS_client");
|
||||
mqttClient.onConnectionLost = onConnectionLost;
|
||||
mqttClient.onMessageArrived = onMessageArrived;
|
||||
mqttClient.connect({ onSuccess: onConnect });
|
||||
|
||||
function onConnect() {
|
||||
console.log("MQTT client connected");
|
||||
mqttClient.subscribe("pahoJS_subscription");
|
||||
}
|
||||
|
||||
function onConnectionLost(responseObject) {
|
||||
if (responseObject.errorCode !== 0) {
|
||||
console.log("onConnectionLost:" + responseObject.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function onMessageArrived(message) {
|
||||
console.log("onMessageArrived:" + message.payloadString);
|
||||
}
|
||||
function buttonClick() {
|
||||
message = new Paho.MQTT.Message("Hello");
|
||||
message.destinationName = "pahoJS_topic";
|
||||
mqttClient.send(message);
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button onclick="buttonClick()">Publish</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -16,8 +16,8 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
public sealed class MqttChannelAdapter : Disposable, IMqttChannelAdapter
|
||||
{
|
||||
const uint _errorOperationAborted = 0x800703E3;
|
||||
const int _readBufferSize = 4096; // TODO: Move buffer size to config
|
||||
const uint ErrorOperationAborted = 0x800703E3;
|
||||
const int ReadBufferSize = 4096;
|
||||
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
readonly IMqttChannel _channel;
|
||||
@@ -25,7 +25,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
||||
|
||||
SemaphoreSlim _writerSemaphore = new SemaphoreSlim(1, 1);
|
||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||
|
||||
long _bytesReceived;
|
||||
long _bytesSent;
|
||||
@@ -58,6 +58,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public async Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
@@ -84,6 +85,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public async Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
@@ -111,43 +113,58 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
{
|
||||
var packetData = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
if (timeout == TimeSpan.Zero)
|
||||
using (await _syncRoot.WaitAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
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);
|
||||
}
|
||||
// Check for cancellation here again because "WaitAsync" might take some time.
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Interlocked.Add(ref _bytesReceived, packetData.Count);
|
||||
try
|
||||
{
|
||||
var packetData = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
_logger.Verbose("TX ({0} bytes) >>> {1}", packetData.Count, 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);
|
||||
|
||||
_logger.Verbose("TX ({0} bytes) >>> {1}", packetData.Count, packet);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
WrapException(exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PacketFormatterAdapter.FreeBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
WrapException(exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PacketFormatterAdapter.FreeBuffer();
|
||||
_writerSemaphore?.Release();
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfDisposed();
|
||||
|
||||
try
|
||||
@@ -187,6 +204,9 @@ namespace MQTTnet.Adapter
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (IsWrappedException(exception))
|
||||
@@ -210,10 +230,8 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_channel?.Dispose();
|
||||
|
||||
_writerSemaphore?.Dispose();
|
||||
_writerSemaphore = null;
|
||||
_channel.Dispose();
|
||||
_syncRoot.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
@@ -245,7 +263,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
var body = new byte[fixedHeader.RemainingLength];
|
||||
var bodyOffset = 0;
|
||||
var chunkSize = Math.Min(_readBufferSize, fixedHeader.RemainingLength);
|
||||
var chunkSize = Math.Min(ReadBufferSize, fixedHeader.RemainingLength);
|
||||
|
||||
do
|
||||
{
|
||||
@@ -304,7 +322,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
if (exception is COMException comException)
|
||||
{
|
||||
if ((uint)comException.HResult == _errorOperationAborted)
|
||||
if ((uint)comException.HResult == ErrorOperationAborted)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace MQTTnet.Certificates
|
||||
{
|
||||
public class BlobCertificateProvider : ICertificateProvider
|
||||
{
|
||||
public BlobCertificateProvider(byte[] blob)
|
||||
{
|
||||
Blob = blob ?? throw new ArgumentNullException(nameof(blob));
|
||||
}
|
||||
|
||||
public byte[] Blob { get; }
|
||||
|
||||
public string Password { get; set; }
|
||||
|
||||
public X509Certificate2 GetCertificate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Password))
|
||||
{
|
||||
// Use a different overload when no password is specified. Otherwise the constructor will fail.
|
||||
return new X509Certificate2(Blob);
|
||||
}
|
||||
|
||||
return new X509Certificate2(Blob, Password);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace MQTTnet.Certificates
|
||||
{
|
||||
public interface ICertificateProvider
|
||||
{
|
||||
X509Certificate2 GetCertificate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#if !WINDOWS_UWP
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace MQTTnet.Certificates
|
||||
{
|
||||
public class X509CertificateProvider : ICertificateProvider
|
||||
{
|
||||
readonly X509Certificate2 _certificate;
|
||||
|
||||
public X509CertificateProvider(X509Certificate2 certificate)
|
||||
{
|
||||
_certificate = certificate ?? throw new ArgumentNullException(nameof(certificate));
|
||||
}
|
||||
|
||||
public X509Certificate2 GetCertificate()
|
||||
{
|
||||
return _certificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -3,6 +3,34 @@
|
||||
public enum MqttClientDisconnectReason
|
||||
{
|
||||
NormalDisconnection = 0,
|
||||
|
||||
DisconnectWithWillMessage = 4,
|
||||
UnspecifiedError = 128,
|
||||
MalformedPacket = 129,
|
||||
ProtocolError = 130,
|
||||
ImplementationSpecificError = 131,
|
||||
NotAuthorized = 135,
|
||||
ServerBusy = 137,
|
||||
ServerShuttingDown = 139,
|
||||
BadAuthenticationMethod = 140,
|
||||
KeepaliveTimeout = 141,
|
||||
SessionTakenOver = 142,
|
||||
TopicFilterInvalid = 143,
|
||||
TopicNameInvalid = 144,
|
||||
ReceiveMaximumExceeded = 147,
|
||||
TopicAliasInvalid = 148,
|
||||
PacketTooLarge = 149,
|
||||
MessageRateTooHigh = 150,
|
||||
QuotaExceeded = 151,
|
||||
AdministrativeAction = 152,
|
||||
PayloadFormatInvalid = 153,
|
||||
RetainNotSupported = 154,
|
||||
QosNotSupported = 155,
|
||||
UseAnotherServer = 156,
|
||||
ServerMoved = 157,
|
||||
SharedSubscriptionsNotSupported = 158,
|
||||
ConnectionRateExceeded = 159,
|
||||
MaximumConnectTime = 160,
|
||||
SubscriptionIdentifiersNotSupported = 161,
|
||||
WildcardSubscriptionsNotSupported = 162
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ namespace MQTTnet.Client.Disconnecting
|
||||
{
|
||||
public class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientAuthenticateResult authenticateResult)
|
||||
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientAuthenticateResult authenticateResult, MqttClientDisconnectReason reasonCode)
|
||||
{
|
||||
ClientWasConnected = clientWasConnected;
|
||||
Exception = exception;
|
||||
AuthenticateResult = authenticateResult;
|
||||
ReasonCode = reasonCode;
|
||||
}
|
||||
|
||||
public bool ClientWasConnected { get; }
|
||||
@@ -17,5 +18,7 @@ namespace MQTTnet.Client.Disconnecting
|
||||
public Exception Exception { get; }
|
||||
|
||||
public MqttClientAuthenticateResult AuthenticateResult { get; }
|
||||
|
||||
public MqttClientDisconnectReason ReasonCode { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using MQTTnet.Adapter;
|
||||
using System;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.LowLevelClient;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
@@ -7,6 +9,14 @@ namespace MQTTnet.Client
|
||||
{
|
||||
IMqttFactory UseClientAdapterFactory(IMqttClientAdapterFactory clientAdapterFactory);
|
||||
|
||||
ILowLevelMqttClient CreateLowLevelMqttClient();
|
||||
|
||||
ILowLevelMqttClient CreateLowLevelMqttClient(IMqttNetLogger logger);
|
||||
|
||||
ILowLevelMqttClient CreateLowLevelMqttClient(IMqttClientAdapterFactory clientAdapterFactory);
|
||||
|
||||
ILowLevelMqttClient CreateLowLevelMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory clientAdapterFactory);
|
||||
|
||||
IMqttClient CreateMqttClient();
|
||||
|
||||
IMqttClient CreateMqttClient(IMqttNetLogger logger);
|
||||
|
||||
@@ -57,13 +57,7 @@ namespace MQTTnet.Client
|
||||
|
||||
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isConnected && Interlocked.Read(ref _isDisconnectPending) == 0;
|
||||
}
|
||||
}
|
||||
public bool IsConnected => _isConnected && Interlocked.Read(ref _isDisconnectPending) == 0;
|
||||
|
||||
public IMqttClientOptions Options { get; private set; }
|
||||
|
||||
@@ -164,9 +158,9 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PingAsync(CancellationToken cancellationToken)
|
||||
public Task PingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false);
|
||||
return SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken)
|
||||
@@ -251,17 +245,22 @@ namespace MQTTnet.Client
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_backgroundCancellationTokenSource?.Dispose();
|
||||
_backgroundCancellationTokenSource = null;
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Dispose();
|
||||
_backgroundCancellationTokenSource = null;
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = null;
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
_adapter?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
@@ -308,7 +307,8 @@ namespace MQTTnet.Client
|
||||
|
||||
async Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientAuthenticateResult authenticateResult)
|
||||
{
|
||||
var clientWasConnected = IsConnected;
|
||||
var clientWasConnected = _isConnected;
|
||||
var reasonCode = MqttClientDisconnectReason.NormalDisconnection;
|
||||
|
||||
TryInitiateDisconnect();
|
||||
_isConnected = false;
|
||||
@@ -326,6 +326,7 @@ namespace MQTTnet.Client
|
||||
catch (Exception adapterException)
|
||||
{
|
||||
_logger.Warning(adapterException, "Error while disconnecting from adapter.");
|
||||
reasonCode = MqttClientDisconnectReason.UnspecifiedError;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -341,6 +342,7 @@ namespace MQTTnet.Client
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Error while waiting for internal tasks.");
|
||||
reasonCode = MqttClientDisconnectReason.UnspecifiedError;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -354,7 +356,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
||||
// when trying to reconnect in that handler etc.
|
||||
Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, authenticateResult))).Forget(_logger);
|
||||
Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, authenticateResult, reasonCode))).Forget(_logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,7 +393,7 @@ namespace MQTTnet.Client
|
||||
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
|
||||
ushort identifier = 0;
|
||||
if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
|
||||
{
|
||||
@@ -440,13 +442,17 @@ namespace MQTTnet.Client
|
||||
{
|
||||
// Values described here: [MQTT-3.1.2-24].
|
||||
var waitTime = keepAlivePeriod - _sendTracker.Elapsed;
|
||||
|
||||
|
||||
if (waitTime <= TimeSpan.Zero)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false);
|
||||
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
|
||||
// due to some edge cases and was buggy in the past. Now we wait half a second because the
|
||||
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING
|
||||
// packet.
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -561,12 +567,15 @@ namespace MQTTnet.Client
|
||||
{
|
||||
await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (packet is MqttDisconnectPacket)
|
||||
else if (packet is MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||
_packetDispatcher.Dispatch(packet);
|
||||
|
||||
await DisconnectAsync(null, cancellationToken).ConfigureAwait(false);
|
||||
await DisconnectAsync(new MqttClientDisconnectOptions()
|
||||
{
|
||||
ReasonCode = (MqttClientDisconnectReason)(disconnectPacket.ReasonCode ?? MqttDisconnectReasonCode.UnspecifiedError)
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (packet is MqttAuthPacket authPacket)
|
||||
{
|
||||
@@ -628,7 +637,6 @@ namespace MQTTnet.Client
|
||||
try
|
||||
{
|
||||
var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken);
|
||||
|
||||
if (!publishPacketDequeueResult.IsSuccess)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -280,7 +280,9 @@ namespace MQTTnet.Client.Options
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
CertificateValidationCallback = _tlsParameters.CertificateValidationCallback,
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
||||
#endif
|
||||
CertificateValidationHandler = _tlsParameters.CertificateValidationHandler,
|
||||
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
|
||||
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors
|
||||
|
||||
@@ -27,7 +27,11 @@ namespace MQTTnet.Client.Options
|
||||
public IEnumerable<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
#if NETCOREAPP3_1
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get;set; }
|
||||
#endif
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ namespace MQTTnet.Client.Options
|
||||
public List<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
#endif
|
||||
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
|
||||
|
||||
[Obsolete("This property will be removed soon. Use CertificateValidationHandler instead.")]
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
return "netstandard2.1";
|
||||
#elif WINDOWS_UWP
|
||||
return "uap10.0";
|
||||
#elif NETCOREAPP3_1
|
||||
return "netcoreapp3.1";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace MQTTnet.Formatter
|
||||
{
|
||||
if (_length < _offset + length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(_buffer), $"Expected at least {_offset + length} bytes but there are only {_length} bytes");
|
||||
throw new MqttProtocolViolationException($"Expected at least {_offset + length} bytes but there are only {_length} bytes");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,14 @@ namespace MQTTnet.Formatter
|
||||
{
|
||||
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
|
||||
|
||||
if (receivedMqttPacket.Body.Length < 7)
|
||||
{
|
||||
// 2 byte protocol name length
|
||||
// at least 4 byte protocol name
|
||||
// 1 byte protocol level
|
||||
throw new MqttProtocolViolationException("CONNECT packet must have at least 7 bytes.");
|
||||
}
|
||||
|
||||
var protocolName = receivedMqttPacket.Body.ReadStringWithLengthPrefix();
|
||||
var protocolLevel = receivedMqttPacket.Body.ReadByte();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MQTTnet.Client;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
|
||||
@@ -7,5 +8,7 @@ namespace MQTTnet
|
||||
public interface IMqttFactory : IMqttClientFactory, IMqttServerFactory
|
||||
{
|
||||
IMqttNetLogger DefaultLogger { get; }
|
||||
|
||||
IDictionary<object, object> Properties { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public CrossPlatformSocket()
|
||||
{
|
||||
// Having this contructor is important because avoiding the address family as parameter
|
||||
// Having this constructor is important because avoiding the address family as parameter
|
||||
// will make use of dual mode in the .net framework.
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
@@ -33,75 +33,34 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public bool NoDelay
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)_socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay) > 0;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0);
|
||||
}
|
||||
get => (int)_socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay) > 0;
|
||||
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0);
|
||||
}
|
||||
|
||||
public bool DualMode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _socket.DualMode;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_socket.DualMode = value;
|
||||
}
|
||||
get => _socket.DualMode;
|
||||
set => _socket.DualMode = value;
|
||||
}
|
||||
|
||||
public int ReceiveBufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _socket.ReceiveBufferSize;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_socket.ReceiveBufferSize = value;
|
||||
}
|
||||
get => _socket.ReceiveBufferSize;
|
||||
set => _socket.ReceiveBufferSize = value;
|
||||
}
|
||||
|
||||
public int SendBufferSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _socket.SendBufferSize;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_socket.SendBufferSize = value;
|
||||
}
|
||||
get => _socket.SendBufferSize;
|
||||
set => _socket.SendBufferSize = value;
|
||||
}
|
||||
|
||||
public EndPoint RemoteEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
return _socket.RemoteEndPoint;
|
||||
}
|
||||
}
|
||||
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
|
||||
|
||||
public bool ReuseAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) != 0;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
|
||||
}
|
||||
get => (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) != 0;
|
||||
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
|
||||
}
|
||||
|
||||
public async Task<CrossPlatformSocket> AcceptAsync()
|
||||
|
||||
@@ -17,14 +17,14 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttTcpChannel : IMqttChannel
|
||||
public sealed class MqttTcpChannel : IMqttChannel
|
||||
{
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
private readonly int _bufferSize;
|
||||
readonly MqttClientTcpOptions _options;
|
||||
readonly int _bufferSize;
|
||||
|
||||
private StreamSocket _socket;
|
||||
private Stream _readStream;
|
||||
private Stream _writeStream;
|
||||
StreamSocket _socket;
|
||||
Stream _readStream;
|
||||
Stream _writeStream;
|
||||
|
||||
public MqttTcpChannel(IMqttClientOptions clientOptions)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpChannel : IDisposable, IMqttChannel
|
||||
public sealed class MqttTcpChannel : IMqttChannel
|
||||
{
|
||||
readonly IMqttClientOptions _clientOptions;
|
||||
readonly MqttClientTcpOptions _options;
|
||||
@@ -81,11 +81,24 @@ namespace MQTTnet.Implementations
|
||||
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
||||
try
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
var sslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
ApplicationProtocols = _options.TlsOptions.ApplicationProtocols,
|
||||
ClientCertificates = LoadCertificates(),
|
||||
EnabledSslProtocols = _options.TlsOptions.SslProtocol,
|
||||
CertificateRevocationCheckMode = _options.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
|
||||
TargetHost = _options.Server
|
||||
};
|
||||
|
||||
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), _options.TlsOptions.SslProtocol, !_options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
#if NETSTANDARD2_1 || NETCOREAPP3_1
|
||||
await sslStream.DisposeAsync().ConfigureAwait(false);
|
||||
#else
|
||||
sslStream.Dispose();
|
||||
@@ -127,7 +140,14 @@ namespace MQTTnet.Implementations
|
||||
// Workaround for: https://github.com/dotnet/corefx/issues/24430
|
||||
using (cancellationToken.Register(Dispose))
|
||||
{
|
||||
return await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
var stream = _stream;
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(stream));
|
||||
}
|
||||
|
||||
return await stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
@@ -157,12 +177,18 @@ namespace MQTTnet.Implementations
|
||||
// Workaround for: https://github.com/dotnet/corefx/issues/24430
|
||||
using (cancellationToken.Register(Dispose))
|
||||
{
|
||||
await _stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
var stream = _stream;
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(stream));
|
||||
}
|
||||
|
||||
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
@@ -196,7 +222,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
#region OBSOLETE
|
||||
#region OBSOLETE
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var certificateValidationCallback = _options?.TlsOptions?.CertificateValidationCallback;
|
||||
@@ -205,8 +231,7 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
return certificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _clientOptions);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
var certificateValidationHandler = _options?.TlsOptions?.CertificateValidationHandler;
|
||||
if (certificateValidationHandler != null)
|
||||
@@ -229,7 +254,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||
if (_options?.TlsOptions?.IgnoreCertificateRevocationErrors != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -237,13 +262,13 @@ namespace MQTTnet.Implementations
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateChainErrors)
|
||||
if (_options?.TlsOptions?.IgnoreCertificateChainErrors != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _options.TlsOptions.AllowUntrustedCertificates;
|
||||
return _options?.TlsOptions?.AllowUntrustedCertificates == true;
|
||||
}
|
||||
|
||||
X509CertificateCollection LoadCertificates()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#if !WINDOWS_UWP
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,7 +12,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpServerAdapter : Disposable, IMqttServerAdapter
|
||||
public sealed class MqttTcpServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>();
|
||||
readonly IMqttNetScopedLogger _logger;
|
||||
@@ -44,22 +43,12 @@ namespace MQTTnet.Implementations
|
||||
|
||||
if (options.TlsEndpointOptions?.IsEnabled == true)
|
||||
{
|
||||
if (options.TlsEndpointOptions.Certificate == null)
|
||||
if (options.TlsEndpointOptions.CertificateProvider == null)
|
||||
{
|
||||
throw new ArgumentException("TLS certificate is not set.");
|
||||
}
|
||||
|
||||
X509Certificate2 tlsCertificate;
|
||||
if (string.IsNullOrEmpty(options.TlsEndpointOptions.CertificateCredentials?.Password))
|
||||
{
|
||||
// Use a different overload when no password is specified. Otherwise the constructor will fail.
|
||||
tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
|
||||
}
|
||||
else
|
||||
{
|
||||
tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate, options.TlsEndpointOptions.CertificateCredentials.Password);
|
||||
}
|
||||
|
||||
|
||||
var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate();
|
||||
if (!tlsCertificate.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
@@ -68,23 +57,18 @@ namespace MQTTnet.Implementations
|
||||
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
Cleanup();
|
||||
return Task.FromResult(0);
|
||||
return PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void Cleanup()
|
||||
@@ -130,7 +114,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
private Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
|
||||
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
|
||||
{
|
||||
var clientHandler = ClientHandler;
|
||||
if (clientHandler == null)
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace MQTTnet.Implementations
|
||||
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
||||
readonly X509Certificate2 _tlsCertificate;
|
||||
|
||||
private CrossPlatformSocket _socket;
|
||||
private IPEndPoint _localEndPoint;
|
||||
CrossPlatformSocket _socket;
|
||||
IPEndPoint _localEndPoint;
|
||||
|
||||
public MqttTcpServerListener(
|
||||
AddressFamily addressFamily,
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
public bool IsSecureConnection { get; private set; }
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; private set; }
|
||||
public X509Certificate2 ClientCertificate { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@ using System.Threading.Tasks;
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
// From Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/)
|
||||
public class AsyncLock : IDisposable
|
||||
public sealed class AsyncLock : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly Task<IDisposable> _releaser;
|
||||
readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
readonly Task<IDisposable> _releaser;
|
||||
|
||||
public AsyncLock()
|
||||
{
|
||||
@@ -39,9 +39,9 @@ namespace MQTTnet.Internal
|
||||
_semaphore?.Dispose();
|
||||
}
|
||||
|
||||
private class Releaser : IDisposable
|
||||
class Releaser : IDisposable
|
||||
{
|
||||
private readonly AsyncLock _toRelease;
|
||||
readonly AsyncLock _toRelease;
|
||||
|
||||
internal Releaser(AsyncLock toRelease)
|
||||
{
|
||||
|
||||
@@ -7,16 +7,15 @@ namespace MQTTnet.Internal
|
||||
{
|
||||
public sealed class AsyncQueue<TItem> : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0);
|
||||
|
||||
private ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>();
|
||||
|
||||
SemaphoreSlim _semaphore = new SemaphoreSlim(0);
|
||||
ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>();
|
||||
|
||||
public int Count => _queue.Count;
|
||||
|
||||
public void Enqueue(TItem item)
|
||||
{
|
||||
_queue.Enqueue(item);
|
||||
_semaphore.Release();
|
||||
_semaphore?.Release();
|
||||
}
|
||||
|
||||
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
|
||||
@@ -25,12 +24,17 @@ namespace MQTTnet.Internal
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_semaphore == null)
|
||||
{
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default);
|
||||
}
|
||||
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default(TItem));
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default);
|
||||
}
|
||||
|
||||
if (_queue.TryDequeue(out var item))
|
||||
@@ -39,7 +43,7 @@ namespace MQTTnet.Internal
|
||||
}
|
||||
}
|
||||
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default(TItem));
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default);
|
||||
}
|
||||
|
||||
public AsyncQueueDequeueResult<TItem> TryDequeue()
|
||||
@@ -49,7 +53,7 @@ namespace MQTTnet.Internal
|
||||
return new AsyncQueueDequeueResult<TItem>(true, item);
|
||||
}
|
||||
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default(TItem));
|
||||
return new AsyncQueueDequeueResult<TItem>(false, default);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
@@ -60,6 +64,7 @@ namespace MQTTnet.Internal
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore?.Dispose();
|
||||
_semaphore = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace MQTTnet.LowLevelClient
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_adapter.Dispose();
|
||||
_adapter?.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' AND '$(SolutionName)' != 'MQTTnet.noUWP' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
@@ -14,6 +14,9 @@
|
||||
<PackageId />
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<DelaySign>false</DelaySign>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||
@@ -41,6 +44,10 @@
|
||||
<DefineConstants>RELEASE;NETSTANDARD1_3</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
|
||||
@@ -6,6 +6,7 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
@@ -25,6 +26,13 @@ namespace MQTTnet
|
||||
|
||||
public IMqttNetLogger DefaultLogger { get; }
|
||||
|
||||
public IList<Func<IMqttFactory, IMqttServerAdapter>> DefaultServerAdapters { get; } = new List<Func<IMqttFactory, IMqttServerAdapter>>
|
||||
{
|
||||
factory => new MqttTcpServerAdapter(factory.DefaultLogger)
|
||||
};
|
||||
|
||||
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
|
||||
|
||||
public IMqttFactory UseClientAdapterFactory(IMqttClientAdapterFactory clientAdapterFactory)
|
||||
{
|
||||
_clientAdapterFactory = clientAdapterFactory ?? throw new ArgumentNullException(nameof(clientAdapterFactory));
|
||||
@@ -50,10 +58,10 @@ namespace MQTTnet
|
||||
return new LowLevelMqttClient(_clientAdapterFactory, DefaultLogger);
|
||||
}
|
||||
|
||||
public ILowLevelMqttClient CreateLowLevelMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory clientAdapterFactoryy)
|
||||
public ILowLevelMqttClient CreateLowLevelMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory clientAdapterFactory)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
if (clientAdapterFactoryy == null) throw new ArgumentNullException(nameof(clientAdapterFactoryy));
|
||||
if (clientAdapterFactory == null) throw new ArgumentNullException(nameof(clientAdapterFactory));
|
||||
|
||||
return new LowLevelMqttClient(_clientAdapterFactory, logger);
|
||||
}
|
||||
@@ -94,7 +102,8 @@ namespace MQTTnet
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
return CreateMqttServer(new List<IMqttServerAdapter> { new MqttTcpServerAdapter(logger) }, logger);
|
||||
var serverAdapters = DefaultServerAdapters.Select(a => a.Invoke(this));
|
||||
return CreateMqttServer(serverAdapters, logger);
|
||||
}
|
||||
|
||||
public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> serverAdapters, IMqttNetLogger logger)
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
[Obsolete("Use MqttTopicFilter instead. It is just a renamed version to align with general namings in this lib.")]
|
||||
[Obsolete("Use MqttTopicFilterBuilder instead. It is just a renamed version to align with general namings in this lib.")]
|
||||
public class TopicFilterBuilder : MqttTopicFilterBuilder
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using MQTTnet.Server.Status;
|
||||
using System;
|
||||
using MQTTnet.Server.Status;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher
|
||||
public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
|
||||
{
|
||||
bool IsStarted { get; }
|
||||
IMqttServerStartedHandler StartedHandler { get; set; }
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
public interface IMqttServerCertificateCredentials
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
string Password { get; }
|
||||
public interface IMqttServerCertificateCredentials
|
||||
{
|
||||
string Password { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
@@ -6,6 +7,8 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public interface IMqttServerFactory
|
||||
{
|
||||
IList<Func<IMqttFactory, IMqttServerAdapter>> DefaultServerAdapters { get; }
|
||||
|
||||
IMqttServer CreateMqttServer();
|
||||
|
||||
IMqttServer CreateMqttServer(IMqttNetLogger logger);
|
||||
|
||||
@@ -25,5 +25,9 @@ namespace MQTTnet.Server
|
||||
IMqttServerStorage Storage { get; }
|
||||
|
||||
IMqttRetainedMessagesManager RetainedMessagesManager { get; }
|
||||
|
||||
IMqttServerApplicationMessageInterceptor UndeliveredMessageInterceptor { get; set; }
|
||||
|
||||
IMqttServerClientDisconnectedHandler ClientDisconnectedInterceptor { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,6 @@ namespace MQTTnet.Server
|
||||
readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
|
||||
|
||||
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
|
||||
readonly Func<Task> _onStart;
|
||||
readonly Func<MqttClientDisconnectType, Task> _onStop;
|
||||
readonly MqttClientKeepAliveMonitor _keepAliveMonitor;
|
||||
readonly MqttClientSessionsManager _sessionsManager;
|
||||
|
||||
@@ -32,6 +30,7 @@ namespace MQTTnet.Server
|
||||
readonly IMqttServerOptions _serverOptions;
|
||||
|
||||
readonly IMqttChannelAdapter _channelAdapter;
|
||||
readonly MqttConnectionValidatorContext _connectionValidatorContext;
|
||||
readonly IMqttDataConverter _dataConverter;
|
||||
readonly string _endpoint;
|
||||
readonly DateTime _connectedTimestamp;
|
||||
@@ -47,25 +46,22 @@ namespace MQTTnet.Server
|
||||
|
||||
volatile bool _isTakeover;
|
||||
|
||||
public MqttClientConnection(
|
||||
MqttConnectPacket connectPacket,
|
||||
public MqttClientConnection(MqttConnectPacket connectPacket,
|
||||
IMqttChannelAdapter channelAdapter,
|
||||
MqttClientSession session,
|
||||
MqttConnectionValidatorContext connectionValidatorContext,
|
||||
IMqttServerOptions serverOptions,
|
||||
MqttClientSessionsManager sessionsManager,
|
||||
IMqttRetainedMessagesManager retainedMessagesManager,
|
||||
Func<Task> onStart,
|
||||
Func<MqttClientDisconnectType, Task> onStop,
|
||||
IMqttNetLogger logger)
|
||||
{
|
||||
Session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
_onStart = onStart ?? throw new ArgumentNullException(nameof(onStart));
|
||||
_onStop = onStop ?? throw new ArgumentNullException(nameof(onStop));
|
||||
|
||||
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||
_connectionValidatorContext = connectionValidatorContext ?? throw new ArgumentNullException(nameof(connectionValidatorContext));
|
||||
_dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter;
|
||||
_endpoint = _channelAdapter.Endpoint;
|
||||
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||
@@ -131,18 +127,17 @@ namespace MQTTnet.Server
|
||||
_cancellationToken.Dispose();
|
||||
}
|
||||
|
||||
public Task RunAsync(MqttConnectionValidatorContext connectionValidatorContext)
|
||||
public Task RunAsync()
|
||||
{
|
||||
_packageReceiverTask = RunInternalAsync(connectionValidatorContext);
|
||||
_packageReceiverTask = RunInternalAsync();
|
||||
return _packageReceiverTask;
|
||||
}
|
||||
|
||||
async Task RunInternalAsync(MqttConnectionValidatorContext connectionValidatorContext)
|
||||
async Task RunInternalAsync()
|
||||
{
|
||||
var disconnectType = MqttClientDisconnectType.NotClean;
|
||||
try
|
||||
{
|
||||
await _onStart();
|
||||
_logger.Info("Client '{0}': Session started.", ClientId);
|
||||
|
||||
_channelAdapter.ReadingPacketStartedCallback = OnAdapterReadingPacketStarted;
|
||||
@@ -155,9 +150,7 @@ namespace MQTTnet.Server
|
||||
// TODO: Change to single thread in SessionManager. Or use SessionManager and stats from KeepAliveMonitor.
|
||||
_keepAliveMonitor.Start(ConnectPacket.KeepAlivePeriod, _cancellationToken.Token);
|
||||
|
||||
await SendAsync(
|
||||
_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext)
|
||||
).ConfigureAwait(false);
|
||||
await SendAsync(_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(_connectionValidatorContext)).ConfigureAwait(false);
|
||||
|
||||
Session.IsCleanSession = false;
|
||||
|
||||
@@ -262,17 +255,33 @@ namespace MQTTnet.Server
|
||||
_channelAdapter.ReadingPacketStartedCallback = null;
|
||||
_channelAdapter.ReadingPacketCompletedCallback = null;
|
||||
|
||||
_logger.Info("Client '{0}': Connection stopped.", ClientId);
|
||||
|
||||
_packageReceiverTask = null;
|
||||
|
||||
if (_isTakeover)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Don't use SendAsync here _cancellationToken is already cancelled.
|
||||
await _channelAdapter.SendPacketAsync(new MqttDisconnectPacket
|
||||
{
|
||||
ReasonCode = MqttDisconnectReasonCode.SessionTakenOver
|
||||
}, TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Client '{0}': Error while sending DISCONNECT packet after takeover.", ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info("Client '{0}': Connection stopped.", ClientId);
|
||||
|
||||
try
|
||||
{
|
||||
await _onStop(disconnectType);
|
||||
await _sessionsManager.CleanUpClient(ClientId, _channelAdapter, disconnectType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "client '{0}': Error while cleaning up", ClientId);
|
||||
_logger.Error(e, "Client '{0}': Error while cleaning up", ClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,22 +401,18 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
publishPacket = _dataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
|
||||
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.QualityOfServiceLevel;
|
||||
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
|
||||
|
||||
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
|
||||
publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel > 0)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
}
|
||||
|
||||
if (_serverOptions.ClientMessageQueueInterceptor != null)
|
||||
{
|
||||
var context = new MqttClientMessageQueueInterceptorContext(
|
||||
queuedApplicationMessage.SenderClientId,
|
||||
ClientId,
|
||||
queuedApplicationMessage.ApplicationMessage);
|
||||
queuedApplicationMessage.ApplicationMessage,
|
||||
queuedApplicationMessage.SubscriptionQualityOfServiceLevel);
|
||||
|
||||
if (_serverOptions.ClientMessageQueueInterceptor != null)
|
||||
{
|
||||
@@ -421,7 +426,12 @@ namespace MQTTnet.Server
|
||||
|
||||
publishPacket.Topic = context.ApplicationMessage.Topic;
|
||||
publishPacket.Payload = context.ApplicationMessage.Payload;
|
||||
publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel;
|
||||
publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel;
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel > 0)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
namespace MQTTnet.Server
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientMessageQueueInterceptorContext
|
||||
{
|
||||
public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage)
|
||||
public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel)
|
||||
{
|
||||
SenderClientId = senderClientId;
|
||||
ReceiverClientId = receiverClientId;
|
||||
ApplicationMessage = applicationMessage;
|
||||
SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel;
|
||||
}
|
||||
|
||||
public string SenderClientId { get; }
|
||||
@@ -16,5 +19,7 @@
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
|
||||
public bool AcceptEnqueue { get; set; } = true;
|
||||
|
||||
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor
|
||||
{
|
||||
private readonly Func<MqttClientMessageQueueInterceptorContext, Task> _callback;
|
||||
|
||||
public MqttClientMessageQueueInterceptorDelegate(Action<MqttClientMessageQueueInterceptorContext> callback)
|
||||
{
|
||||
if (callback == null) throw new ArgumentNullException(nameof(callback));
|
||||
|
||||
_callback = context =>
|
||||
{
|
||||
callback(context);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
}
|
||||
|
||||
public MqttClientMessageQueueInterceptorDelegate(Func<MqttClientMessageQueueInterceptorContext, Task> callback)
|
||||
{
|
||||
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
public Task InterceptClientMessageQueueEnqueueAsync(MqttClientMessageQueueInterceptorContext context)
|
||||
{
|
||||
return _callback(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,17 +46,19 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public IDictionary<object, object> Items { get; }
|
||||
|
||||
public void EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage)
|
||||
public bool EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage)
|
||||
{
|
||||
var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel);
|
||||
if (!checkSubscriptionsResult.IsSubscribed)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.Verbose("Queued application message with topic '{0}' (ClientId: {1}).", applicationMessage.Topic, ClientId);
|
||||
|
||||
ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters)
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
ApplicationMessage = applicationMessage,
|
||||
SenderClientId = senderClientId,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
SubscriptionQualityOfServiceLevel = qualityOfServiceLevel,
|
||||
IsRetainedMessage = isRetainedMessage
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSessionsManager : Disposable
|
||||
public sealed class MqttClientSessionsManager : IDisposable
|
||||
{
|
||||
readonly AsyncQueue<MqttEnqueuedApplicationMessage> _messageQueue = new AsyncQueue<MqttEnqueuedApplicationMessage>();
|
||||
|
||||
readonly AsyncLock _createConnectionGate = new AsyncLock();
|
||||
readonly ConcurrentDictionary<string, MqttClientConnection> _connections = new ConcurrentDictionary<string, MqttClientConnection>();
|
||||
readonly ConcurrentDictionary<string, MqttClientSession> _sessions = new ConcurrentDictionary<string, MqttClientSession>();
|
||||
|
||||
readonly IDictionary<object, object> _serverSessionItems = new ConcurrentDictionary<object, object>();
|
||||
|
||||
readonly CancellationToken _cancellationToken;
|
||||
@@ -150,13 +151,9 @@ namespace MQTTnet.Server
|
||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_messageQueue?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
_messageQueue?.Dispose();
|
||||
}
|
||||
|
||||
async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken)
|
||||
@@ -187,7 +184,6 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!dequeueResult.IsSuccess)
|
||||
{
|
||||
return;
|
||||
@@ -224,12 +220,31 @@ namespace MQTTnet.Server
|
||||
await _retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var deliveryCount = 0;
|
||||
|
||||
foreach (var clientSession in _sessions.Values)
|
||||
{
|
||||
clientSession.EnqueueApplicationMessage(
|
||||
var isSubscribed = clientSession.EnqueueApplicationMessage(
|
||||
applicationMessage,
|
||||
sender?.ClientId,
|
||||
false);
|
||||
|
||||
if (isSubscribed)
|
||||
{
|
||||
deliveryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveryCount == 0)
|
||||
{
|
||||
var undeliveredMessageInterceptor = _options.UndeliveredMessageInterceptor;
|
||||
|
||||
if (undeliveredMessageInterceptor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await undeliveredMessageInterceptor.InterceptApplicationMessagePublishAsync(new MqttApplicationMessageInterceptorContext(sender?.ClientId, sender?.Session?.Items, applicationMessage));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -243,11 +258,9 @@ namespace MQTTnet.Server
|
||||
|
||||
async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
string clientId = null;
|
||||
|
||||
MqttConnectPacket connectPacket;
|
||||
try
|
||||
{
|
||||
MqttConnectPacket connectPacket;
|
||||
try
|
||||
{
|
||||
var firstPacket = await channelAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false);
|
||||
@@ -276,17 +289,9 @@ namespace MQTTnet.Server
|
||||
return;
|
||||
}
|
||||
|
||||
clientId = connectPacket.ClientId;
|
||||
|
||||
var connection = await CreateClientConnectionAsync(
|
||||
connectPacket,
|
||||
connectionValidatorContext,
|
||||
channelAdapter,
|
||||
async () => await _eventDispatcher.SafeNotifyClientConnectedAsync(clientId).ConfigureAwait(false),
|
||||
async disconnectType => await CleanUpClient(clientId, channelAdapter, disconnectType)
|
||||
).ConfigureAwait(false);
|
||||
|
||||
await connection.RunAsync(connectionValidatorContext).ConfigureAwait(false);
|
||||
var connection = await CreateClientConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false);
|
||||
await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket.ClientId).ConfigureAwait(false);
|
||||
await connection.RunAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -297,15 +302,19 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CleanUpClient(string clientId, IMqttChannelAdapter channelAdapter, MqttClientDisconnectType disconnectType)
|
||||
public async Task CleanUpClient(string clientId, IMqttChannelAdapter channelAdapter, MqttClientDisconnectType disconnectType)
|
||||
{
|
||||
if (clientId != null)
|
||||
{
|
||||
_connections.TryRemove(clientId, out _);
|
||||
|
||||
if (!_options.EnablePersistentSessions)
|
||||
// in case it is a takeover _connections already contains the new connection
|
||||
if (disconnectType != MqttClientDisconnectType.Takeover)
|
||||
{
|
||||
await DeleteSessionAsync(clientId).ConfigureAwait(false);
|
||||
_connections.TryRemove(clientId, out _);
|
||||
|
||||
if (!_options.EnablePersistentSessions)
|
||||
{
|
||||
await DeleteSessionAsync(clientId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,45 +354,52 @@ namespace MQTTnet.Server
|
||||
return context;
|
||||
}
|
||||
|
||||
async Task<MqttClientConnection> CreateClientConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter, Func<Task> onStart, Func<MqttClientDisconnectType, Task> onStop)
|
||||
async Task<MqttClientConnection> CreateClientConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter)
|
||||
{
|
||||
MqttClientConnection connection;
|
||||
MqttClientConnection existingConnection = null;
|
||||
|
||||
using (await _createConnectionGate.WaitAsync(_cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var session);
|
||||
|
||||
var isConnectionPresent = _connections.TryGetValue(connectPacket.ClientId, out var existingConnection);
|
||||
if (isConnectionPresent)
|
||||
{
|
||||
await existingConnection.StopAsync(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (isSessionPresent)
|
||||
{
|
||||
if (connectPacket.CleanSession)
|
||||
var session = _sessions.AddOrUpdate(
|
||||
connectPacket.ClientId,
|
||||
key =>
|
||||
{
|
||||
session = null;
|
||||
|
||||
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
else
|
||||
_logger.Verbose("Created a new session for client '{0}'.", key);
|
||||
return new MqttClientSession(key, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _retainedMessagesManager, _rootLogger);
|
||||
},
|
||||
(key, existingSession) =>
|
||||
{
|
||||
if (connectPacket.CleanSession)
|
||||
{
|
||||
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
|
||||
return new MqttClientSession(key, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _retainedMessagesManager, _rootLogger);
|
||||
}
|
||||
|
||||
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
}
|
||||
return existingSession;
|
||||
});
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
session = new MqttClientSession(connectPacket.ClientId, connectionValidatorContext.SessionItems, _eventDispatcher, _options, _retainedMessagesManager, _rootLogger);
|
||||
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
connection = new MqttClientConnection(connectPacket, channelAdapter, session, connectionValidatorContext, _options, this, _retainedMessagesManager, _rootLogger);
|
||||
|
||||
var connection = new MqttClientConnection(connectPacket, channelAdapter, session, _options, this, _retainedMessagesManager, onStart, onStop, _rootLogger);
|
||||
|
||||
_connections[connection.ClientId] = connection;
|
||||
_sessions[session.ClientId] = session;
|
||||
|
||||
return connection;
|
||||
_connections.AddOrUpdate(
|
||||
connectPacket.ClientId,
|
||||
key => connection,
|
||||
(key, tempExistingConnection) =>
|
||||
{
|
||||
existingConnection = tempExistingConnection;
|
||||
return connection;
|
||||
});
|
||||
}
|
||||
|
||||
// Disconnect the client outside of the lock so that new clients can still connect while a single
|
||||
// one is being disconnected.
|
||||
if (existingConnection != null)
|
||||
{
|
||||
await existingConnection.StopAsync(true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(MqttClientConnection senderConnection, MqttApplicationMessage applicationMessage)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MQTTnet.Protocol;
|
||||
using System;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
@@ -10,7 +11,14 @@ namespace MQTTnet.Server
|
||||
|
||||
public bool IsRetainedMessage { get; set; }
|
||||
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
|
||||
|
||||
[Obsolete("Use 'SubscriptionQualityOfServiceLevel' instead.")]
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel
|
||||
{
|
||||
get => SubscriptionQualityOfServiceLevel;
|
||||
set => SubscriptionQualityOfServiceLevel = value;
|
||||
}
|
||||
|
||||
public bool IsDuplicate { get; set; }
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServer : IMqttServer
|
||||
public class MqttServer : Disposable, IMqttServer
|
||||
{
|
||||
readonly MqttServerEventDispatcher _eventDispatcher;
|
||||
readonly ICollection<IMqttServerAdapter> _adapters;
|
||||
@@ -76,23 +78,43 @@ namespace MQTTnet.Server
|
||||
|
||||
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _clientSessionsManager.GetClientStatusAsync();
|
||||
}
|
||||
|
||||
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _clientSessionsManager.GetSessionStatusAsync();
|
||||
}
|
||||
|
||||
public Task<IList<MqttApplicationMessage>> GetRetainedApplicationMessagesAsync()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _retainedMessagesManager.GetMessagesAsync();
|
||||
}
|
||||
|
||||
public Task ClearRetainedApplicationMessagesAsync()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _retainedMessagesManager?.ClearMessagesAsync() ?? PlatformAbstractionLayer.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
|
||||
{
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _clientSessionsManager.SubscribeAsync(clientId, topicFilters);
|
||||
}
|
||||
@@ -102,6 +124,9 @@ namespace MQTTnet.Server
|
||||
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
||||
}
|
||||
|
||||
@@ -109,9 +134,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
MqttTopicValidator.ThrowIfInvalid(applicationMessage.Topic);
|
||||
|
||||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started.");
|
||||
ThrowIfNotStarted();
|
||||
|
||||
_clientSessionsManager.DispatchApplicationMessage(applicationMessage, null);
|
||||
|
||||
@@ -120,16 +147,16 @@ namespace MQTTnet.Server
|
||||
|
||||
public async Task StartAsync(IMqttServerOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfStarted();
|
||||
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (Options.RetainedMessagesManager == null) throw new MqttConfigurationException("options.RetainedMessagesManager should not be null.");
|
||||
|
||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started.");
|
||||
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_retainedMessagesManager = Options.RetainedMessagesManager;
|
||||
await _retainedMessagesManager.Start(Options, _rootLogger);
|
||||
_retainedMessagesManager = Options.RetainedMessagesManager ?? throw new MqttConfigurationException("options.RetainedMessagesManager should not be null.");
|
||||
|
||||
await _retainedMessagesManager.Start(Options, _rootLogger).ConfigureAwait(false);
|
||||
await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false);
|
||||
|
||||
_clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _cancellationTokenSource.Token, _eventDispatcher, _rootLogger);
|
||||
@@ -173,13 +200,13 @@ namespace MQTTnet.Server
|
||||
}
|
||||
finally
|
||||
{
|
||||
_clientSessionsManager?.Dispose();
|
||||
_clientSessionsManager = null;
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_retainedMessagesManager = null;
|
||||
|
||||
_clientSessionsManager?.Dispose();
|
||||
_clientSessionsManager = null;
|
||||
}
|
||||
|
||||
var stoppedHandler = StoppedHandler;
|
||||
@@ -189,14 +216,40 @@ namespace MQTTnet.Server
|
||||
}
|
||||
}
|
||||
|
||||
public Task ClearRetainedApplicationMessagesAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
return _retainedMessagesManager?.ClearMessagesAsync();
|
||||
if (disposing)
|
||||
{
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
|
||||
foreach (var adapter in _adapters)
|
||||
{
|
||||
adapter.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
Task OnHandleClient(IMqttChannelAdapter channelAdapter)
|
||||
{
|
||||
return _clientSessionsManager.HandleClientConnectionAsync(channelAdapter);
|
||||
}
|
||||
|
||||
void ThrowIfStarted()
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
throw new InvalidOperationException("The MQTT server is already started.");
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowIfNotStarted()
|
||||
{
|
||||
if (_cancellationTokenSource == null)
|
||||
{
|
||||
throw new InvalidOperationException("The MQTT server is not started.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,9 @@ namespace MQTTnet.Server
|
||||
public IMqttServerStorage Storage { get; set; }
|
||||
|
||||
public IMqttRetainedMessagesManager RetainedMessagesManager { get; set; } = new MqttRetainedMessagesManager();
|
||||
|
||||
public IMqttServerApplicationMessageInterceptor UndeliveredMessageInterceptor { get; set; }
|
||||
|
||||
public IMqttServerClientDisconnectedHandler ClientDisconnectedInterceptor { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using MQTTnet.Certificates;
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
#endif
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerOptionsBuilder
|
||||
{
|
||||
private readonly MqttServerOptions _options = new MqttServerOptions();
|
||||
|
||||
readonly MqttServerOptions _options = new MqttServerOptions();
|
||||
|
||||
public MqttServerOptionsBuilder WithConnectionBacklog(int value)
|
||||
{
|
||||
_options.DefaultEndpointOptions.ConnectionBacklog = value;
|
||||
@@ -82,13 +87,28 @@ namespace MQTTnet.Server
|
||||
return this;
|
||||
}
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
|
||||
{
|
||||
_options.TlsEndpointOptions.Certificate = value;
|
||||
_options.TlsEndpointOptions.CertificateCredentials = credentials;
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value)
|
||||
{
|
||||
Password = credentials?.Password
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
|
||||
return this;
|
||||
}
|
||||
#endif
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value)
|
||||
{
|
||||
_options.TlsEndpointOptions.SslProtocol = value;
|
||||
@@ -143,6 +163,18 @@ namespace MQTTnet.Server
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithDisconnectedInterceptor(IMqttServerClientDisconnectedHandler value)
|
||||
{
|
||||
_options.ClientDisconnectedInterceptor = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithDisconnectedInterceptor(Action<MqttServerClientDisconnectedEventArgs> value)
|
||||
{
|
||||
_options.ClientDisconnectedInterceptor = new MqttServerClientDisconnectedHandlerDelegate(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithApplicationMessageInterceptor(IMqttServerApplicationMessageInterceptor value)
|
||||
{
|
||||
_options.ApplicationMessageInterceptor = value;
|
||||
@@ -155,6 +187,18 @@ namespace MQTTnet.Server
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithClientMessageQueueInterceptor(IMqttServerClientMessageQueueInterceptor value)
|
||||
{
|
||||
_options.ClientMessageQueueInterceptor = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithClientMessageQueueInterceptor(Action<MqttClientMessageQueueInterceptorContext> value)
|
||||
{
|
||||
_options.ClientMessageQueueInterceptor = new MqttClientMessageQueueInterceptorDelegate(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithSubscriptionInterceptor(IMqttServerSubscriptionInterceptor value)
|
||||
{
|
||||
_options.SubscriptionInterceptor = value;
|
||||
@@ -173,6 +217,18 @@ namespace MQTTnet.Server
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithDefaultEndpointReuseAddress()
|
||||
{
|
||||
_options.DefaultEndpointOptions.ReuseAddress = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithTlsEndpointReuseAddress()
|
||||
{
|
||||
_options.TlsEndpointOptions.ReuseAddress = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithPersistentSessions()
|
||||
{
|
||||
_options.EnablePersistentSessions = true;
|
||||
@@ -192,5 +248,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithUndeliveredMessageInterceptor(Action<MqttApplicationMessageInterceptorContext> value)
|
||||
{
|
||||
_options.UndeliveredMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,56 @@
|
||||
using System.Net.Security;
|
||||
using System;
|
||||
using System.Security.Authentication;
|
||||
using MQTTnet.Certificates;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions
|
||||
{
|
||||
ICertificateProvider _certificateProvider;
|
||||
|
||||
public MqttServerTlsTcpEndpointOptions()
|
||||
{
|
||||
Port = 8883;
|
||||
}
|
||||
|
||||
[Obsolete("Please use CertificateProvider with 'BlobCertificateProvider' instead.")]
|
||||
public byte[] Certificate { get; set; }
|
||||
|
||||
[Obsolete("Please use CertificateProvider with 'BlobCertificateProvider' including password property instead.")]
|
||||
public IMqttServerCertificateCredentials CertificateCredentials { get; set; }
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
|
||||
#endif
|
||||
public ICertificateProvider CertificateProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
// Backward compatibility only. Gets converted to auto property when
|
||||
// obsolete properties are removed.
|
||||
if (_certificateProvider != null)
|
||||
{
|
||||
return _certificateProvider;
|
||||
}
|
||||
|
||||
if (Certificate == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BlobCertificateProvider(Certificate)
|
||||
{
|
||||
Password = CertificateCredentials?.Password
|
||||
};
|
||||
}
|
||||
|
||||
set => _certificateProvider = value;
|
||||
}
|
||||
|
||||
public bool ClientCertificateRequired { get; set; }
|
||||
|
||||
public bool CheckCertificateRevocation { get; set; }
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
|
||||
#endif
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' != 'netcoreapp3.1' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
|
||||
|
||||
@@ -15,6 +15,8 @@ using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System.Net;
|
||||
using MQTTnet.AspNetCore.Extensions;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
@@ -34,28 +36,46 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
|
||||
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestParallelWrites()
|
||||
public async Task TestCorruptedConnectPacket()
|
||||
{
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311);
|
||||
var writer = new MqttPacketWriter();
|
||||
var serializer = new MqttPacketFormatterAdapter(writer);
|
||||
var pipe = new DuplexPipeMockup();
|
||||
var connection = new DefaultConnectionContext();
|
||||
connection.Transport = pipe;
|
||||
var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
|
||||
|
||||
var tasks = Enumerable.Range(1, 10).Select(_ => Task.Run(async () =>
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}));
|
||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None));
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
|
||||
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None));
|
||||
}
|
||||
|
||||
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
|
||||
//[TestMethod]
|
||||
//public async Task TestParallelWrites()
|
||||
//{
|
||||
// var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311);
|
||||
// var pipe = new DuplexPipeMockup();
|
||||
// var connection = new DefaultConnectionContext();
|
||||
// connection.Transport = pipe;
|
||||
// var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
// var tasks = Enumerable.Range(1, 100).Select(_ => Task.Run(async () =>
|
||||
// {
|
||||
// for (int i = 0; i < 100; i++)
|
||||
// {
|
||||
// await ctx.SendPacketAsync(new MqttPublishPacket(), TimeSpan.Zero, CancellationToken.None).ConfigureAwait(false);
|
||||
// }
|
||||
// }));
|
||||
|
||||
// await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
//}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestLargePacket()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#if NETCOREAPP
|
||||
#if NETCOREAPP3_1
|
||||
using System.Buffers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.AspNetCore.Extensions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -31,7 +32,6 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
result = serializer.TryDecode(reader, part, out packet, out consumed, out observed, out read);
|
||||
Assert.IsFalse(result);
|
||||
|
||||
|
||||
part = sequence.Slice(sequence.Start, 1); // partial fixed header should fail
|
||||
result = serializer.TryDecode(reader, part, out packet, out consumed, out observed, out read);
|
||||
Assert.IsFalse(result);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Tests;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class SpanBasedMqttPacketWriterTests : MqttPacketWriter_Tests
|
||||
{
|
||||
protected override IMqttPacketWriter WriterFactory()
|
||||
{
|
||||
return new SpanBasedMqttPacketWriter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.1" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.AspNetCore.Client;
|
||||
using MQTTnet.AspNetCore.Extensions;
|
||||
using MQTTnet.Client.Options;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Formatter
|
||||
{
|
||||
public static class MqttPacketWriterExtensions
|
||||
{
|
||||
public static byte[] AddMqttHeader(this IMqttPacketWriter writer, MqttControlPacketType header, byte[] body)
|
||||
{
|
||||
writer.Write(MqttPacketWriter.BuildFixedHeader(header));
|
||||
writer.WriteVariableLengthInteger((uint)body.Length);
|
||||
writer.Write(body, 0, body.Length);
|
||||
return writer.GetBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Exceptions;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
@@ -16,6 +17,18 @@ namespace MQTTnet.Tests
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Not_Existing_Server()
|
||||
{
|
||||
var client = new MqttFactory().CreateLowLevelMqttClient();
|
||||
var options = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer("localhost")
|
||||
.Build();
|
||||
|
||||
await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Connect_And_Disconnect()
|
||||
{
|
||||
@@ -78,7 +91,7 @@ namespace MQTTnet.Tests
|
||||
|
||||
async Task<MqttConnAckPacket> Authenticate(ILowLevelMqttClient client)
|
||||
{
|
||||
await client.SendAsync(new MqttConnectPacket()
|
||||
await client.SendAsync(new MqttConnectPacket
|
||||
{
|
||||
CleanSession = true,
|
||||
ClientId = TestContext.TestName,
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Client.Options;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -11,13 +10,10 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
public sealed class TestEnvironment : Disposable
|
||||
public sealed class TestEnvironment : IDisposable
|
||||
{
|
||||
readonly MqttFactory _mqttFactory = new MqttFactory();
|
||||
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
||||
readonly IMqttNetLogger _serverLogger = new MqttNetLogger("server");
|
||||
readonly IMqttNetLogger _clientLogger = new MqttNetLogger("client");
|
||||
|
||||
readonly List<string> _serverErrors = new List<string>();
|
||||
readonly List<string> _clientErrors = new List<string>();
|
||||
|
||||
@@ -31,9 +27,9 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public int ServerPort { get; set; } = 1888;
|
||||
|
||||
public IMqttNetLogger ServerLogger => _serverLogger;
|
||||
public IMqttNetLogger ServerLogger { get; } = new MqttNetLogger("server");
|
||||
|
||||
public IMqttNetLogger ClientLogger => _clientLogger;
|
||||
public IMqttNetLogger ClientLogger { get; } = new MqttNetLogger("client");
|
||||
|
||||
public TestContext TestContext { get; }
|
||||
|
||||
@@ -45,7 +41,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
TestContext = testContext;
|
||||
|
||||
_serverLogger.LogMessagePublished += (s, e) =>
|
||||
ServerLogger.LogMessagePublished += (s, e) =>
|
||||
{
|
||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||
{
|
||||
@@ -56,7 +52,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
};
|
||||
|
||||
_clientLogger.LogMessagePublished += (s, e) =>
|
||||
ClientLogger.LogMessagePublished += (s, e) =>
|
||||
{
|
||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||
{
|
||||
@@ -70,7 +66,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public IMqttClient CreateClient()
|
||||
{
|
||||
var client = _mqttFactory.CreateMqttClient(_clientLogger);
|
||||
var client = _mqttFactory.CreateMqttClient(ClientLogger);
|
||||
_clients.Add(client);
|
||||
|
||||
return new TestClientWrapper(client, TestContext);
|
||||
@@ -83,13 +79,18 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (Server != null)
|
||||
{
|
||||
throw new InvalidOperationException("Server already started.");
|
||||
}
|
||||
|
||||
Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(_serverLogger), TestContext, this);
|
||||
await Server.StartAsync(options.WithDefaultEndpointPort(ServerPort).Build());
|
||||
Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this);
|
||||
|
||||
options.WithDefaultEndpointPort(ServerPort);
|
||||
|
||||
await Server.StartAsync(options.Build()).ConfigureAwait(false);
|
||||
|
||||
return Server;
|
||||
}
|
||||
@@ -106,7 +107,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
options = options.WithTcpServer("localhost", ServerPort);
|
||||
|
||||
var client = CreateClient();
|
||||
await client.ConnectAsync(options.Build());
|
||||
await client.ConnectAsync(options.Build()).ConfigureAwait(false);
|
||||
|
||||
return client;
|
||||
}
|
||||
@@ -116,7 +117,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
var client = CreateClient();
|
||||
await client.ConnectAsync(options);
|
||||
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||
|
||||
return client;
|
||||
}
|
||||
@@ -140,26 +141,22 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
foreach (var mqttClient in _clients)
|
||||
{
|
||||
foreach (var mqttClient in _clients)
|
||||
{
|
||||
mqttClient?.Dispose();
|
||||
}
|
||||
|
||||
Server?.StopAsync().GetAwaiter().GetResult();
|
||||
|
||||
ThrowIfLogErrors();
|
||||
|
||||
if (_exceptions.Any())
|
||||
{
|
||||
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
|
||||
}
|
||||
mqttClient?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
Server?.StopAsync().GetAwaiter().GetResult();
|
||||
Server?.Dispose();
|
||||
|
||||
ThrowIfLogErrors();
|
||||
|
||||
if (_exceptions.Any())
|
||||
{
|
||||
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
|
||||
}
|
||||
}
|
||||
|
||||
public void TrackException(Exception exception)
|
||||
|
||||
@@ -132,5 +132,9 @@ namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
return Implementation.UnsubscribeAsync(clientId, topicFilters);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,38 @@ namespace MQTTnet.Tests
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
public async Task Set_ClientWasConnected_On_ServerDisconnect()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
var server = await testEnvironment.StartServerAsync();
|
||||
var client = await testEnvironment.ConnectClientAsync();
|
||||
|
||||
Assert.IsTrue(client.IsConnected);
|
||||
client.UseDisconnectedHandler(e => Assert.IsTrue(e.ClientWasConnected));
|
||||
|
||||
await server.StopAsync();
|
||||
await Task.Delay(4000);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Set_ClientWasConnected_On_ClientDisconnect()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
var server = await testEnvironment.StartServerAsync();
|
||||
var client = await testEnvironment.ConnectClientAsync();
|
||||
|
||||
Assert.IsTrue(client.IsConnected);
|
||||
client.UseDisconnectedHandler(e => Assert.IsTrue(e.ClientWasConnected));
|
||||
|
||||
await client.DisconnectAsync();
|
||||
await Task.Delay(200);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
@@ -96,7 +128,7 @@ namespace MQTTnet.Tests
|
||||
[TestMethod]
|
||||
public async Task Send_Reply_In_Message_Handler()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
using (var testEnvironment = new TestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServerAsync();
|
||||
var client1 = await testEnvironment.ConnectClientAsync();
|
||||
@@ -125,6 +157,8 @@ namespace MQTTnet.Tests
|
||||
|
||||
await client1.PublishAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.IsTrue(replyReceived);
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Formatter.V3;
|
||||
using MQTTnet.Formatter.V5;
|
||||
@@ -18,6 +19,31 @@ namespace MQTTnet.Tests
|
||||
[TestClass]
|
||||
public class MqttPacketSerializer_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void DetectVersionFromMqttConnectPacket()
|
||||
{
|
||||
var p = new MqttConnectPacket
|
||||
{
|
||||
ClientId = "XYZ",
|
||||
Password = Encoding.UTF8.GetBytes("PASS"),
|
||||
Username = "USER",
|
||||
KeepAlivePeriod = 123,
|
||||
CleanSession = true
|
||||
};
|
||||
var adapter = new MqttPacketFormatterAdapter(WriterFactory());
|
||||
|
||||
Assert.AreEqual(MqttProtocolVersion.V310, DeserializeAndDetectVersion(adapter, Serialize(p, MqttProtocolVersion.V310)));
|
||||
Assert.AreEqual(MqttProtocolVersion.V311, DeserializeAndDetectVersion(adapter, Serialize(p, MqttProtocolVersion.V311)));
|
||||
Assert.AreEqual(MqttProtocolVersion.V500, DeserializeAndDetectVersion(adapter, Serialize(p, MqttProtocolVersion.V500)));
|
||||
|
||||
var ex = Assert.ThrowsException<MqttProtocolViolationException>(() => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[0])));
|
||||
Assert.AreEqual("CONNECT packet must have at least 7 bytes.", ex.Message);
|
||||
ex = Assert.ThrowsException<MqttProtocolViolationException>(() => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[7])));
|
||||
Assert.AreEqual("Protocol '' not supported.", ex.Message);
|
||||
ex = Assert.ThrowsException<MqttProtocolViolationException>(() => DeserializeAndDetectVersion(adapter, WriterFactory().AddMqttHeader(MqttControlPacketType.Connect, new byte[] { 255, 255, 0,0,0,0,0 })));
|
||||
Assert.AreEqual("Expected at least 65537 bytes but there are only 7 bytes", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SerializeV310_MqttConnectPacket()
|
||||
{
|
||||
@@ -156,7 +182,7 @@ namespace MQTTnet.Tests
|
||||
[TestMethod]
|
||||
public void Serialize_LargePacket()
|
||||
{
|
||||
var serializer = new MqttV311PacketFormatter(new MqttPacketWriter());
|
||||
var serializer = new MqttV311PacketFormatter(WriterFactory());
|
||||
|
||||
const int payloadLength = 80000;
|
||||
|
||||
@@ -555,25 +581,15 @@ namespace MQTTnet.Tests
|
||||
DeserializeAndCompare(p, "sAIAew==");
|
||||
}
|
||||
|
||||
private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
private void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
{
|
||||
IMqttPacketFormatter serializer;
|
||||
if (protocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
serializer = new MqttV311PacketFormatter(new MqttPacketWriter());
|
||||
}
|
||||
else if (protocolVersion == MqttProtocolVersion.V310)
|
||||
{
|
||||
serializer = new MqttV310PacketFormatter(new MqttPacketWriter());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Serialize(packet, protocolVersion)));
|
||||
}
|
||||
|
||||
var data = serializer.Encode(packet);
|
||||
|
||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data)));
|
||||
private byte[] Serialize(MqttBasePacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
{
|
||||
var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, WriterFactory());
|
||||
return Join(serializer.Encode(packet));
|
||||
}
|
||||
|
||||
protected virtual IMqttPacketWriter WriterFactory()
|
||||
@@ -635,6 +651,25 @@ namespace MQTTnet.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter adapter, byte[] buffer)
|
||||
{
|
||||
using (var headerStream = new MemoryStream(buffer))
|
||||
{
|
||||
var channel = new TestMqttChannel(headerStream);
|
||||
var fixedHeader = new byte[2];
|
||||
|
||||
var header = new MqttPacketReader(channel).ReadFixedHeaderAsync(fixedHeader, CancellationToken.None).GetAwaiter().GetResult().FixedHeader;
|
||||
|
||||
using (var bodyStream = new MemoryStream(buffer, (int)headerStream.Position, (int)header.RemainingLength))
|
||||
{
|
||||
var reader = ReaderFactory(bodyStream.ToArray());
|
||||
var packet = new ReceivedMqttPacket(header.Flags, reader, 0);
|
||||
adapter.DetectProtocolVersion(packet);
|
||||
return adapter.ProtocolVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
{
|
||||
var buffer = new MemoryStream();
|
||||
|
||||
@@ -6,10 +6,15 @@ namespace MQTTnet.Tests
|
||||
[TestClass]
|
||||
public class MqttPacketWriter_Tests
|
||||
{
|
||||
protected virtual IMqttPacketWriter WriterFactory()
|
||||
{
|
||||
return new MqttPacketWriter();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WritePacket()
|
||||
{
|
||||
var writer = new MqttPacketWriter();
|
||||
var writer = WriterFactory();
|
||||
Assert.AreEqual(0, writer.Length);
|
||||
|
||||
writer.WriteWithLengthPrefix("1234567890");
|
||||
|
||||
@@ -274,6 +274,82 @@ namespace MQTTnet.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Subscribe_Lots_In_Single_Request()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
await testEnvironment.StartServerAsync();
|
||||
|
||||
var c1 = await testEnvironment.ConnectClientAsync();
|
||||
c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount));
|
||||
|
||||
var optionsBuilder = new MqttClientSubscribeOptionsBuilder();
|
||||
for (var i = 0; i < 500; i++)
|
||||
{
|
||||
optionsBuilder.WithTopicFilter(i.ToString(), MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
await c1.SubscribeAsync(optionsBuilder.Build()).ConfigureAwait(false);
|
||||
|
||||
var c2 = await testEnvironment.ConnectClientAsync();
|
||||
|
||||
var messageBuilder = new MqttApplicationMessageBuilder();
|
||||
for (var i = 0; i < 500; i++)
|
||||
{
|
||||
messageBuilder.WithTopic(i.ToString());
|
||||
|
||||
await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
SpinWait.SpinUntil(() => receivedMessagesCount == 500, 5000);
|
||||
|
||||
Assert.AreEqual(500, receivedMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Subscribe_Lots_In_Multiple_Requests()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
await testEnvironment.StartServerAsync();
|
||||
|
||||
var c1 = await testEnvironment.ConnectClientAsync();
|
||||
c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount));
|
||||
|
||||
for (var i = 0; i < 500; i++)
|
||||
{
|
||||
var so = new MqttClientSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(i.ToString()).Build();
|
||||
|
||||
await c1.SubscribeAsync(so).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
var c2 = await testEnvironment.ConnectClientAsync();
|
||||
|
||||
var messageBuilder = new MqttApplicationMessageBuilder();
|
||||
for (var i = 0; i < 500; i++)
|
||||
{
|
||||
messageBuilder.WithTopic(i.ToString());
|
||||
|
||||
await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
SpinWait.SpinUntil(() => receivedMessagesCount == 500, 5000);
|
||||
|
||||
Assert.AreEqual(500, receivedMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Subscribe_Multiple_In_Multiple_Request()
|
||||
{
|
||||
@@ -1312,5 +1388,95 @@ namespace MQTTnet.Tests
|
||||
Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Undelivered()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment())
|
||||
{
|
||||
var undeliverd = string.Empty;
|
||||
|
||||
var options = new MqttServerOptionsBuilder().WithUndeliveredMessageInterceptor(
|
||||
context =>
|
||||
{
|
||||
undeliverd = context.ApplicationMessage.Topic;
|
||||
});
|
||||
|
||||
await testEnvironment.StartServerAsync(options);
|
||||
|
||||
var client = await testEnvironment.ConnectClientAsync();
|
||||
|
||||
await client.SubscribeAsync("b");
|
||||
|
||||
await client.PublishAsync("a", null, MqttQualityOfServiceLevel.ExactlyOnce);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
Assert.AreEqual("a", undeliverd);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_ClientMessageQueue()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()
|
||||
.WithClientMessageQueueInterceptor(c => c.ApplicationMessage.Topic = "a"));
|
||||
|
||||
var topicAReceived = false;
|
||||
var topicBReceived = false;
|
||||
|
||||
var client = await testEnvironment.ConnectClientAsync();
|
||||
client.UseApplicationMessageReceivedHandler(c =>
|
||||
{
|
||||
if (c.ApplicationMessage.Topic == "a")
|
||||
{
|
||||
topicAReceived = true;
|
||||
}
|
||||
else if (c.ApplicationMessage.Topic == "b")
|
||||
{
|
||||
topicBReceived = true;
|
||||
}
|
||||
});
|
||||
|
||||
await client.SubscribeAsync("a");
|
||||
await client.SubscribeAsync("b");
|
||||
|
||||
await client.PublishAsync("b");
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
Assert.IsTrue(topicAReceived);
|
||||
Assert.IsFalse(topicBReceived);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_ClientMessageQueue_Different_QoS_Of_Subscription_And_Message()
|
||||
{
|
||||
const string topic = "a";
|
||||
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()
|
||||
.WithClientMessageQueueInterceptor(c => { })); // Interceptor does nothing but has to be present.
|
||||
|
||||
bool receivedMessage = false;
|
||||
var client = await testEnvironment.ConnectClientAsync();
|
||||
client.UseApplicationMessageReceivedHandler(c =>
|
||||
{
|
||||
receivedMessage = true;
|
||||
});
|
||||
|
||||
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
await client.PublishAsync(new MqttApplicationMessage{ Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
Assert.IsTrue(receivedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Client.Options;
|
||||
using MQTTnet.Client.Subscribing;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
@@ -84,5 +85,40 @@ namespace MQTTnet.Tests
|
||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task Manage_Session_MaxParallel()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
var serverOptions = new MqttServerOptionsBuilder();
|
||||
await testEnvironment.StartServerAsync(serverOptions);
|
||||
|
||||
var options = new MqttClientOptionsBuilder()
|
||||
.WithClientId("1")
|
||||
;
|
||||
|
||||
var clients = await Task.WhenAll(Enumerable.Range(0, 10)
|
||||
.Select(i => TryConnect(testEnvironment, options)));
|
||||
|
||||
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
|
||||
|
||||
Assert.AreEqual(1, connectedClients.Count);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await testEnvironment.ConnectClientAsync(options);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="3.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||
<ProjectReference Include="..\Shared\MQTTnet.Test.BlazorApp.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
@page "/counter"
|
||||
@using MQTTnet.Client.Options
|
||||
@using System.Threading
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private async Task IncrementCount()
|
||||
{
|
||||
var mqttFactory = new MqttFactory();
|
||||
var client = mqttFactory.CreateMqttClient();
|
||||
|
||||
var options = new MqttClientOptionsBuilder()
|
||||
.WithWebSocketServer("192.168.1.15:80/mqtt")
|
||||
.Build();
|
||||
|
||||
await client.ConnectAsync(options, CancellationToken.None);
|
||||
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
@page "/fetchdata"
|
||||
@using MQTTnet.Test.BlazorApp.Shared
|
||||
@inject HttpClient Http
|
||||
|
||||
<h1>Weather forecast</h1>
|
||||
|
||||
<p>This component demonstrates fetching data from the server.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Temp. (C)</th>
|
||||
<th>Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[] forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@page "/"
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
<SurveyPrompt Title="How is Blazor working for you?" />
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace MQTTnet.Test.BlazorApp.Client
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static Task Main(string[] args)
|
||||
{
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("app");
|
||||
|
||||
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
|
||||
return builder.Build().RunAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="sidebar">
|
||||
<NavMenu />
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="top-row px-4">
|
||||
<a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
|
||||
</div>
|
||||
|
||||
<div class="content px-4">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user