mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
109
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c1da6a21 | ||
|
|
4e3ede42c6 | ||
|
|
918a3c8d8b | ||
|
|
290419b3b6 | ||
|
|
32b6146532 | ||
|
|
3a73ec21f7 | ||
|
|
41d5b70344 | ||
|
|
b4769778af | ||
|
|
473f9c31b1 | ||
|
|
ae4f168066 | ||
|
|
c5578a93e8 | ||
|
|
f4b2a10616 | ||
|
|
ed63aa4910 | ||
|
|
4481869d42 | ||
|
|
abbf14b1ec | ||
|
|
4f9e54ba1d | ||
|
|
d80188167a | ||
|
|
4ab956abd1 | ||
|
|
1aa91e3828 | ||
|
|
2410d7b4c1 | ||
|
|
b7328e5fd2 | ||
|
|
7f5c437c46 | ||
|
|
ae5e038548 | ||
|
|
67e0fb8b0b | ||
|
|
b763542329 | ||
|
|
d061da6077 | ||
|
|
82a235aef4 | ||
|
|
fd55982d98 | ||
|
|
aabf180b23 | ||
|
|
1245d8299a | ||
|
|
1ffffaff47 | ||
|
|
c204f6d8b2 | ||
|
|
b775fb256b | ||
|
|
f82d382f23 | ||
|
|
4214af283a | ||
|
|
b32a5c9fa0 | ||
|
|
e7e13a631f | ||
|
|
c12096705f | ||
|
|
eb25500f50 | ||
|
|
4df06980a1 | ||
|
|
f3499013f4 | ||
|
|
158a7fca46 | ||
|
|
09ab30fb0f | ||
|
|
ae84aa43d2 | ||
|
|
b431a5d3fe | ||
|
|
b9683c2943 | ||
|
|
1ba8f74555 | ||
|
|
ec637f7946 | ||
|
|
2599b3dfdc | ||
|
|
d22caa7b70 | ||
|
|
01c90badf9 | ||
|
|
e4f605fbd5 | ||
|
|
6fdee9e0db | ||
|
|
cae7206733 | ||
|
|
e2e7aacc09 | ||
|
|
0d3dd96f2a | ||
|
|
e0660ba887 | ||
|
|
5965ded488 | ||
|
|
fc31c3077c | ||
|
|
e604345e65 | ||
|
|
111a0d69cf | ||
|
|
1a08ec17ba | ||
|
|
c537d290c3 | ||
|
|
2180f59725 | ||
|
|
b1dc8607af | ||
|
|
101898695e | ||
|
|
9295626503 | ||
|
|
18614d4e81 | ||
|
|
560e80455f | ||
|
|
a97e88be29 | ||
|
|
3b5667c882 | ||
|
|
0790e54794 | ||
|
|
94f9fac2bd | ||
|
|
8ad86928f1 | ||
|
|
d8e5936d45 | ||
|
|
caa717544b | ||
|
|
66b77f8d6d | ||
|
|
ab5264c8ca | ||
|
|
db12309bae | ||
|
|
2953e0dcc5 | ||
|
|
1b18ac9fe3 | ||
|
|
2cdad6622a | ||
|
|
7601d11a7a | ||
|
|
0bb4c6ef09 | ||
|
|
449624fd72 | ||
|
|
99f4f46062 | ||
|
|
b08d11c069 | ||
|
|
ac41e838ec | ||
|
|
836ddf3bbc | ||
|
|
0e815c626c | ||
|
|
4b7d035e9e | ||
|
|
cb3c44f48d | ||
|
|
8b3b9d33de | ||
|
|
59d99509c8 | ||
|
|
2933b76237 | ||
|
|
36c6d8974e | ||
|
|
d6cab76728 | ||
|
|
b4f740f338 | ||
|
|
a004de25ac | ||
|
|
d0584413ca | ||
|
|
ba57537108 | ||
|
|
6dcb110737 | ||
|
|
b599a832da | ||
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 |
+1
-2
@@ -40,7 +40,6 @@ dotnet_style_qualification_for_method = false:suggestion
|
|||||||
dotnet_style_qualification_for_property = false:suggestion
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
|
|
||||||
# ReSharper properties
|
# ReSharper properties
|
||||||
resharper_autodetect_indent_settings = true
|
|
||||||
resharper_default_private_modifier = implicit
|
resharper_default_private_modifier = implicit
|
||||||
resharper_place_accessorholder_attribute_on_same_line = false
|
resharper_place_accessorholder_attribute_on_same_line = false
|
||||||
resharper_use_indent_from_vs = false
|
resharper_use_indent_from_vs = false
|
||||||
@@ -61,7 +60,7 @@ resharper_web_config_wrong_module_highlighting = warning
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = crlf
|
end_of_line = crlf
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|||||||
@@ -1,8 +1 @@
|
|||||||
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
|
* [Client] Restored _Server_ and _Port_ behavior of client options (#2005).
|
||||||
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
|
|
||||||
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
|
|
||||||
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
|
|
||||||
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
|
|
||||||
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
|
|
||||||
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
|
|
||||||
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.2.0.${{github.run_number}}"
|
VERSION: "4.3.6.${{github.run_number}}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
uses: darenm/Setup-VSTest@v1
|
uses: darenm/Setup-VSTest@v1
|
||||||
|
|
||||||
- name: Core Tests
|
- name: Core Tests
|
||||||
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
|
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net7.0\MQTTnet.Tests.dll
|
||||||
|
|
||||||
- name: ASP.NET Tests
|
- name: ASP.NET Tests
|
||||||
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
|
||||||
|
|||||||
+13
-9
@@ -7,9 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\M
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
|
||||||
@@ -20,17 +20,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocket4Net", "Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj", "{2BD01D53-4CA5-4142-BE8D-313876395E3E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocket4Net", "Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj", "{2BD01D53-4CA5-4142-BE8D-313876395E3E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspNetCore.Tests", "Source\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Source\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{A238BBBF-C75F-482D-9CC3-BB34ABA9B675}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.TopicTemplate", "Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj", "{374D5861-F7A6-42A2-8CBE-040846000EF9}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -82,12 +84,14 @@ Global
|
|||||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
@@ -230,6 +230,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Licensed to the .NET Foundation under one or more agreements.
|
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Licensed to the .NET Foundation under one or more agreements.
|
||||||
The .NET Foundation licenses this file to you under the MIT license.
|
The .NET Foundation licenses this file to you under the MIT license.
|
||||||
See the LICENSE file in the project root for more information.</s:String>
|
See the LICENSE file in the project root for more information.</s:String>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
@@ -240,6 +241,7 @@ See the LICENSE file in the project root for more information.</s:String>
|
|||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=catched/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@@ -50,6 +50,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
|||||||
* A custom message interceptor can be added which allows transforming or extending every received application message
|
* A custom message interceptor can be added which allows transforming or extending every received application message
|
||||||
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
|
||||||
|
|
||||||
|
Use these command in the Package Manager console to install MQTTnet manually:
|
||||||
|
```
|
||||||
|
Install-Package MQTTnet
|
||||||
|
```
|
||||||
|
|
||||||
|
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
|
||||||
|
|
||||||
|
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
|
||||||
|
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
|
||||||
|
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
|
||||||
|
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.WebSocket4Net;
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
@@ -135,15 +136,15 @@ public static class Client_Connection_Samples
|
|||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
// certificates. This should not be used in live environments.
|
// certificates. This should not be used in live environments.
|
||||||
o.CertificateValidationHandler = _ => true;
|
o.WithCertificateValidationHandler(_ => true);
|
||||||
|
|
||||||
// The default value is determined by the OS. Set manually to force version.
|
// The default value is determined by the OS. Set manually to force version.
|
||||||
o.SslProtocol = SslProtocols.Tls12;
|
o.WithSslProtocols(SslProtocols.Tls12);
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -170,7 +171,7 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||||
|
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||||
|
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -215,13 +216,11 @@ public static class Client_Connection_Samples
|
|||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o => o.WithCertificateValidationHandler(
|
||||||
{
|
|
||||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
// certificates. This should not be used in live environments.
|
// certificates. This should not be used in live environments.
|
||||||
o.CertificateValidationHandler = _ => true;
|
_ => true))
|
||||||
})
|
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// In MQTTv5 the response contains much more information.
|
// In MQTTv5 the response contains much more information.
|
||||||
@@ -236,6 +235,31 @@ public static class Client_Connection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Connect_With_Amazon_AWS()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
|
||||||
|
*
|
||||||
|
* The broker requires special settings which are set here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||||
|
// Disabling packet fragmentation is very important!
|
||||||
|
.WithoutPacketFragmentation()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
|
||||||
|
await mqttClient.DisconnectAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Disconnect_Clean()
|
public static async Task Disconnect_Clean()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -291,10 +315,11 @@ public static class Client_Connection_Samples
|
|||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
o.CertificateValidationHandler = eventArgs =>
|
o.WithCertificateValidationHandler(
|
||||||
|
eventArgs =>
|
||||||
{
|
{
|
||||||
eventArgs.Certificate.Subject.DumpToConsole();
|
eventArgs.Certificate.Subject.DumpToConsole();
|
||||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||||
@@ -302,7 +327,7 @@ public static class Client_Connection_Samples
|
|||||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||||
return true;
|
return true;
|
||||||
};
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -406,6 +431,59 @@ public static class Client_Connection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Console.WriteLine("Press <Enter> to exit");
|
||||||
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task ConnectTls_WithCaFile()
|
||||||
|
{
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
X509Certificate2Collection caChain = new X509Certificate2Collection();
|
||||||
|
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("test.mosquitto.org", 8883)
|
||||||
|
.WithTlsOptions(new MqttClientTlsOptionsBuilder()
|
||||||
|
.WithTrustChain(caChain)
|
||||||
|
.Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
|
||||||
|
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
const string mosquitto_org = @"
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
|
||||||
|
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
|
||||||
|
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
|
||||||
|
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
|
||||||
|
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
|
||||||
|
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
|
||||||
|
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
|
||||||
|
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
|
||||||
|
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
|
||||||
|
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
|
||||||
|
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
|
||||||
|
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
|
||||||
|
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
|
||||||
|
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
|
||||||
|
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
|
||||||
|
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
|
||||||
|
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
|
||||||
|
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
|
||||||
|
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
|
||||||
|
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
";
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
// ReSharper disable UnusedType.Global
|
// ReSharper disable UnusedType.Global
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
// ReSharper disable UnusedMember.Local
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -176,4 +177,62 @@ public static class Client_Subscribe_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
|
||||||
|
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
|
||||||
|
*/
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += ea =>
|
||||||
|
{
|
||||||
|
ea.AutoAcknowledge = false;
|
||||||
|
|
||||||
|
async Task ProcessAsync()
|
||||||
|
{
|
||||||
|
// DO YOUR WORK HERE!
|
||||||
|
await Task.Delay(1000, shutdownToken);
|
||||||
|
await ea.AcknowledgeAsync(shutdownToken);
|
||||||
|
// WARNING: If process failures are not transient the message will be retried on every restart of the client
|
||||||
|
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
|
||||||
|
// the broker know processing failed
|
||||||
|
//
|
||||||
|
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample shows how to achieve concurrent processing, with:
|
||||||
|
* - a maximum concurrency limit based on Environment.ProcessorCount
|
||||||
|
*/
|
||||||
|
|
||||||
|
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
|
||||||
|
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += async ea =>
|
||||||
|
{
|
||||||
|
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
async Task ProcessAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// DO YOUR WORK HERE!
|
||||||
|
await Task.Delay(1000, shutdownToken);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
concurrent.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
|
|||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
mqttClient.InspectPackage += OnInspectPackage;
|
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
|
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Extensions.ManagedClient;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.ManagedClient;
|
||||||
|
|
||||||
|
public sealed class Managed_Client_Subscribe_Samples
|
||||||
|
{
|
||||||
|
public static async Task Connect_Client()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample creates a simple managed MQTT client and connects to a public broker, subscribe to a topic and verifies subscription result.
|
||||||
|
*
|
||||||
|
* The managed client extends the existing _MqttClient_. It adds the following features.
|
||||||
|
* - Reconnecting when connection is lost.
|
||||||
|
* - Storing pending messages in an internal queue so that an enqueue is possible while the client remains not connected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
var subscribed = false;
|
||||||
|
|
||||||
|
using (var managedMqttClient = mqttFactory.CreateManagedMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("broker.hivemq.com")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var managedMqttClientOptions = new ManagedMqttClientOptionsBuilder()
|
||||||
|
.WithClientOptions(mqttClientOptions)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await managedMqttClient.StartAsync(managedMqttClientOptions);
|
||||||
|
|
||||||
|
// The application message is not sent. It is stored in an internal queue and
|
||||||
|
// will be sent when the client is connected.
|
||||||
|
await managedMqttClient.EnqueueAsync("Topic", "Payload");
|
||||||
|
|
||||||
|
Console.WriteLine("The managed MQTT client is connected.");
|
||||||
|
|
||||||
|
// Wait until the queue is fully processed.
|
||||||
|
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
|
||||||
|
|
||||||
|
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
|
||||||
|
|
||||||
|
managedMqttClient.SubscriptionsChangedAsync += args => SubscriptionsResultAsync(args, subscribed);
|
||||||
|
await managedMqttClient.SubscribeAsync("Topic").ConfigureAwait(false);
|
||||||
|
|
||||||
|
SpinWait.SpinUntil(() => subscribed, 1000);
|
||||||
|
Console.WriteLine("Subscription properly done");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task SubscriptionsResultAsync(SubscriptionsChangedEventArgs arg, bool subscribed)
|
||||||
|
{
|
||||||
|
foreach (var mqttClientSubscribeResult in arg.SubscribeResult)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Subscription reason {mqttClientSubscribeResult.ReasonString}");
|
||||||
|
foreach (var item in mqttClientSubscribeResult.Items)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"For topic filter {item.TopicFilter}, result code: {item.ResultCode}");
|
||||||
|
|
||||||
|
if (item.TopicFilter.Topic == "Topic" && item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS0 && !subscribed)
|
||||||
|
{
|
||||||
|
subscribed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var mqttClientUnsubscribeResult in arg.UnsubscribeResult)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Unsubscription reason {mqttClientUnsubscribeResult.ReasonString}");
|
||||||
|
foreach (var item in mqttClientUnsubscribeResult.Items)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"For topic filter {item.TopicFilter}, result code: {item.ResultCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-1
@@ -4,7 +4,13 @@
|
|||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
Console.WriteLine("Welcome to MQTTnet samples!");
|
Console.BackgroundColor = ConsoleColor.White;
|
||||||
|
Console.BackgroundColor = ConsoleColor.Red;
|
||||||
|
Console.Title = "Samples - MQTTnet";
|
||||||
|
Console.WriteLine("----------------------------------------------");
|
||||||
|
Console.WriteLine("- Welcome to MQTTnet samples! -");
|
||||||
|
Console.WriteLine("----------------------------------------------");
|
||||||
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||||
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("Please choose sample class (press Enter to continue): ");
|
Console.Write("Please choose sample class (press Enter to continue): ");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
var input = Console.ReadLine();
|
var input = Console.ReadLine();
|
||||||
var selectedIndex = int.Parse(input ?? "0");
|
var selectedIndex = int.Parse(input ?? "0");
|
||||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||||
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("Please choose sample (press Enter to continue): ");
|
Console.Write("Please choose sample (press Enter to continue): ");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
input = Console.ReadLine();
|
input = Console.ReadLine();
|
||||||
selectedIndex = int.Parse(input ?? "0");
|
selectedIndex = int.Parse(input ?? "0");
|
||||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
Console.WriteLine("Executing sample...");
|
Console.WriteLine("Executing sample...");
|
||||||
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ using MQTTnet.Server;
|
|||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
public static class Server_Asp_Net_Samples
|
public static class Server_ASP_NET_Samples
|
||||||
{
|
{
|
||||||
public static Task Start_Server_With_WebSockets_Support()
|
public static Task Start_Server_With_WebSockets_Support()
|
||||||
{
|
{
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||||
|
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||||
|
|
||||||
|
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||||
Console.WriteLine("Retained messages loaded.");
|
Console.WriteLine("Retained messages loaded.");
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
@@ -56,7 +61,9 @@ public static class Server_Retained_Messages_Samples
|
|||||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||||
// of retained messages must be provided in the loaded event.
|
// of retained messages must be provided in the loaded event.
|
||||||
|
|
||||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||||
|
|
||||||
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||||
await File.WriteAllBytesAsync(storePath, buffer);
|
await File.WriteAllBytesAsync(storePath, buffer);
|
||||||
Console.WriteLine("Retained messages saved.");
|
Console.WriteLine("Retained messages saved.");
|
||||||
}
|
}
|
||||||
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
|
|||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class MqttRetainedMessageModel
|
||||||
|
{
|
||||||
|
public string? ContentType { get; set; }
|
||||||
|
public byte[]? CorrelationData { get; set; }
|
||||||
|
public byte[]? Payload { get; set; }
|
||||||
|
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
|
||||||
|
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||||
|
public string? ResponseTopic { get; set; }
|
||||||
|
public string? Topic { get; set; }
|
||||||
|
public List<MqttUserProperty>? UserProperties { get; set; }
|
||||||
|
|
||||||
|
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||||
|
{
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttRetainedMessageModel
|
||||||
|
{
|
||||||
|
Topic = message.Topic,
|
||||||
|
|
||||||
|
// Create a copy of the buffer from the payload segment because
|
||||||
|
// it cannot be serialized and deserialized with the JSON serializer.
|
||||||
|
Payload = message.PayloadSegment.ToArray(),
|
||||||
|
UserProperties = message.UserProperties,
|
||||||
|
ResponseTopic = message.ResponseTopic,
|
||||||
|
CorrelationData = message.CorrelationData,
|
||||||
|
ContentType = message.ContentType,
|
||||||
|
PayloadFormatIndicator = message.PayloadFormatIndicator,
|
||||||
|
QualityOfServiceLevel = message.QualityOfServiceLevel
|
||||||
|
|
||||||
|
// Other properties like "Retain" are not if interest in the storage.
|
||||||
|
// That's why a custom model makes sense.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessage ToApplicationMessage()
|
||||||
|
{
|
||||||
|
return new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = Topic,
|
||||||
|
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
|
||||||
|
PayloadFormatIndicator = PayloadFormatIndicator,
|
||||||
|
ResponseTopic = ResponseTopic,
|
||||||
|
CorrelationData = CorrelationData,
|
||||||
|
ContentType = ContentType,
|
||||||
|
UserProperties = UserProperties,
|
||||||
|
QualityOfServiceLevel = QualityOfServiceLevel,
|
||||||
|
Dup = false,
|
||||||
|
Retain = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_TLS_Samples
|
||||||
|
{
|
||||||
|
public static async Task Run_Server_With_Self_Signed_Certificate()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||||
|
* It also has an encrypted connection using a self signed TLS certificate.
|
||||||
|
*
|
||||||
|
* See sample "Run_Minimal_Server" for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
// This certificate is self signed so that
|
||||||
|
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||||
|
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
|
||||||
|
|
||||||
|
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
|
||||||
|
// Stop and dispose the MQTT server if it is no longer needed!
|
||||||
|
await mqttServer.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||||
|
{
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null!,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,6 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
{
|
{
|
||||||
var mockup = new ConnectionHandlerMockup();
|
var mockup = new ConnectionHandlerMockup();
|
||||||
|
|
||||||
|
|
||||||
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
|
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
.ConfigureServices(
|
.ConfigureServices(
|
||||||
@@ -103,7 +102,7 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
connection.Transport = pipe;
|
connection.Transport = pipe;
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
var ctx = new MqttConnectionContext(serializer, connection);
|
||||||
|
|
||||||
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
|
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var readResult = await pipe.Send.Reader.ReadAsync();
|
var readResult = await pipe.Send.Reader.ReadAsync();
|
||||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
{
|
{
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
|
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
|
||||||
|
|
||||||
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
|
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using MQTTnet.Adapter;
|
|||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
using MQTTnet.AspNetCore.Client.Tcp;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
@@ -22,8 +21,7 @@ namespace MQTTnet.AspNetCore.Client
|
|||||||
{
|
{
|
||||||
case MqttClientTcpOptions tcpOptions:
|
case MqttClientTcpOptions tcpOptions:
|
||||||
{
|
{
|
||||||
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
|
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
|
||||||
var tcpConnection = new TcpConnection(endpoint);
|
|
||||||
|
|
||||||
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
|
||||||
return new MqttConnectionContext(formatter, tcpConnection);
|
return new MqttConnectionContext(formatter, tcpConnection);
|
||||||
|
|||||||
+17
-16
@@ -15,15 +15,15 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Client.Tcp
|
namespace MQTTnet.AspNetCore.Client.Tcp
|
||||||
{
|
{
|
||||||
public class TcpConnection : ConnectionContext
|
public sealed class SocketConnection : ConnectionContext
|
||||||
{
|
{
|
||||||
private volatile bool _aborted;
|
volatile bool _aborted;
|
||||||
private readonly EndPoint _endPoint;
|
readonly EndPoint _endPoint;
|
||||||
private SocketSender _sender;
|
SocketSender _sender;
|
||||||
private SocketReceiver _receiver;
|
SocketReceiver _receiver;
|
||||||
|
|
||||||
private Socket _socket;
|
Socket _socket;
|
||||||
private IDuplexPipe _application;
|
IDuplexPipe _application;
|
||||||
|
|
||||||
public bool IsConnected { get; private set; }
|
public bool IsConnected { get; private set; }
|
||||||
public override string ConnectionId { get; set; }
|
public override string ConnectionId { get; set; }
|
||||||
@@ -31,12 +31,12 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
public override IDictionary<object, object> Items { get; set; }
|
public override IDictionary<object, object> Items { get; set; }
|
||||||
public override IDuplexPipe Transport { get; set; }
|
public override IDuplexPipe Transport { get; set; }
|
||||||
|
|
||||||
public TcpConnection(EndPoint endPoint)
|
public SocketConnection(EndPoint endPoint)
|
||||||
{
|
{
|
||||||
_endPoint = endPoint;
|
_endPoint = endPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TcpConnection(Socket socket)
|
public SocketConnection(Socket socket)
|
||||||
{
|
{
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
_endPoint = socket.RemoteEndPoint;
|
_endPoint = socket.RemoteEndPoint;
|
||||||
@@ -87,7 +87,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteAsync()
|
async Task ExecuteAsync()
|
||||||
{
|
{
|
||||||
Exception sendError = null;
|
Exception sendError = null;
|
||||||
try
|
try
|
||||||
@@ -114,7 +114,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Unexpected exception in {nameof(TcpConnection)}.{nameof(StartAsync)}: " + ex);
|
Console.WriteLine($"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}: " + ex);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -122,7 +122,8 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
_application.Input.Complete(sendError);
|
_application.Input.Complete(sendError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async Task DoReceive()
|
|
||||||
|
async Task DoReceive()
|
||||||
{
|
{
|
||||||
Exception error = null;
|
Exception error = null;
|
||||||
|
|
||||||
@@ -171,7 +172,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessReceives()
|
async Task ProcessReceives()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -204,12 +205,12 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Exception ConnectionAborted()
|
Exception ConnectionAborted()
|
||||||
{
|
{
|
||||||
return new MqttCommunicationException("Connection Aborted");
|
return new MqttCommunicationException("Connection Aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Exception> DoSend()
|
async Task<Exception> DoSend()
|
||||||
{
|
{
|
||||||
Exception error = null;
|
Exception error = null;
|
||||||
|
|
||||||
@@ -240,7 +241,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessSends()
|
async Task ProcessSends()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -51,11 +51,11 @@
|
|||||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
|
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(TargetFramework)' != 'netcoreapp2.1' ">
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
|
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,12 @@ namespace MQTTnet.AspNetCore
|
|||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||||
|
|
||||||
|
if (!(_connection is SocketConnection tcp) || tcp.IsConnected)
|
||||||
|
{
|
||||||
_input = connection.Transport.Input;
|
_input = connection.Transport.Input;
|
||||||
_output = connection.Transport.Output;
|
_output = connection.Transport.Output;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long BytesReceived { get; private set; }
|
public long BytesReceived { get; private set; }
|
||||||
|
|
||||||
@@ -80,8 +83,6 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsReadingPacket { get; private set; }
|
|
||||||
|
|
||||||
public bool IsSecureConnection
|
public bool IsSecureConnection
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -108,7 +109,7 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_connection is TcpConnection tcp && !tcp.IsConnected)
|
if (_connection is SocketConnection tcp && !tcp.IsConnected)
|
||||||
{
|
{
|
||||||
await tcp.StartAsync().ConfigureAwait(false);
|
await tcp.StartAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -161,11 +162,6 @@ namespace MQTTnet.AspNetCore
|
|||||||
BytesReceived += received;
|
BytesReceived += received;
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// we did receive something but the message is not yet complete
|
|
||||||
IsReadingPacket = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (readResult.IsCompleted)
|
else if (readResult.IsCompleted)
|
||||||
{
|
{
|
||||||
@@ -186,12 +182,9 @@ namespace MQTTnet.AspNetCore
|
|||||||
// completing the channel makes sure that there is no more data read after a protocol error
|
// completing the channel makes sure that there is no more data read after a protocol error
|
||||||
_input?.Complete(exception);
|
_input?.Complete(exception);
|
||||||
_output?.Complete(exception);
|
_output?.Complete(exception);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsReadingPacket = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,20 +15,27 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
public sealed class MqttHostedServer : MqttServer, IHostedService
|
public sealed class MqttHostedServer : MqttServer, IHostedService
|
||||||
{
|
{
|
||||||
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
readonly MqttFactory _mqttFactory;
|
||||||
: base(options, adapters, logger)
|
|
||||||
|
public MqttHostedServer(MqttFactory mqttFactory, MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
|
||||||
|
options,
|
||||||
|
adapters,
|
||||||
|
logger)
|
||||||
{
|
{
|
||||||
|
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// The yield makes sure that the hosted service is considered up and running.
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
_ = StartAsync();
|
_ = StartAsync();
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return StopAsync();
|
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,38 +2,34 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||||
{
|
{
|
||||||
IMqttNetLogger _logger = new MqttNetNullLogger();
|
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
|
||||||
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
if (webSocket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(webSocket));
|
||||||
|
}
|
||||||
|
|
||||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
||||||
|
|
||||||
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
|
|||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
||||||
|
|
||||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
|
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
|
||||||
{
|
{
|
||||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -60,8 +56,15 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
@@ -14,6 +15,89 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddSingleton(options);
|
||||||
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||||
|
|
||||||
|
configure?.Invoke(serverOptionsBuilder);
|
||||||
|
|
||||||
|
var options = serverOptionsBuilder.Build();
|
||||||
|
|
||||||
|
return AddHostedMqttServer(services, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddHostedMqttServer(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// The user may have these services already registered.
|
||||||
|
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
|
||||||
|
services.TryAddSingleton(new MqttFactory());
|
||||||
|
|
||||||
|
services.AddSingleton<MqttHostedServer>();
|
||||||
|
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
||||||
|
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddSingleton(
|
||||||
|
s =>
|
||||||
|
{
|
||||||
|
var builder = new AspNetMqttServerOptionsBuilder(s);
|
||||||
|
configure(builder);
|
||||||
|
return builder.Build();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MqttConnectionHandler>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddSingleton(logger);
|
||||||
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||||
{
|
{
|
||||||
if (serviceCollection is null)
|
if (serviceCollection is null)
|
||||||
@@ -27,53 +111,10 @@ namespace MQTTnet.AspNetCore
|
|||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
services.AddSingleton<MqttTcpServerAdapter>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||||
services.AddSingleton(options);
|
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
|
|
||||||
{
|
|
||||||
services.AddSingleton(s =>
|
|
||||||
{
|
|
||||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
|
||||||
configure?.Invoke(serverOptionsBuilder);
|
|
||||||
return serverOptionsBuilder.Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
|
||||||
{
|
|
||||||
services.AddSingleton(s =>
|
|
||||||
{
|
|
||||||
var builder = new AspNetMqttServerOptionsBuilder(s);
|
|
||||||
configure(builder);
|
|
||||||
return builder.Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
var logger = new MqttNetEventLogger();
|
|
||||||
|
|
||||||
services.AddSingleton<IMqttNetLogger>(logger);
|
|
||||||
services.AddSingleton<MqttHostedServer>();
|
|
||||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
|
||||||
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@@ -85,21 +126,5 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<MqttTcpServerAdapter>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<MqttConnectionHandler>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var channel = new MemoryMqttChannel(_stream);
|
var channel = new MemoryMqttChannel(_stream);
|
||||||
|
|
||||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var writer = new MqttBufferWriter(4096, 65535);
|
var writer = new MqttBufferWriter(4096, 65535);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(0x01);
|
writer.WriteByte(0x01);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574489);
|
writer.WriteVariableByteInteger(8574489);
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
|
|
||||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||||
}
|
}
|
||||||
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
|
|||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574589);
|
writer.WriteVariableByteInteger(8574589);
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
|
|
||||||
writer.Reset(0);
|
writer.Reset(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
public void Deserialize_10000_Messages()
|
public void Deserialize_10000_Messages()
|
||||||
{
|
{
|
||||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||||
|
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var task = Task.Run(() => server.AcceptSocket());
|
var task = Task.Run(() => server.AcceptSocket());
|
||||||
|
|
||||||
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
|
||||||
|
|
||||||
clientConnection.StartAsync().GetAwaiter().GetResult();
|
clientConnection.StartAsync().GetAwaiter().GetResult();
|
||||||
_client = clientConnection.Transport;
|
_client = clientConnection.Transport;
|
||||||
|
|
||||||
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
|
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
|
||||||
serverConnection.StartAsync().GetAwaiter().GetResult();
|
serverConnection.StartAsync().GetAwaiter().GetResult();
|
||||||
_server = serverConnection.Transport;
|
_server = serverConnection.Transport;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
||||||
|
|
||||||
|
event Func<SubscriptionsChangedEventArgs, Task> SubscriptionsChangedAsync;
|
||||||
|
|
||||||
IMqttClient InternalClient { get; }
|
IMqttClient InternalClient { get; }
|
||||||
|
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MQTTnet.Extensions.ManagedClient
|
||||||
|
{
|
||||||
|
public sealed class InterceptingPublishMessageEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public InterceptingPublishMessageEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||||
|
{
|
||||||
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||||
|
|
||||||
|
public bool AcceptPublish { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
||||||
|
|||||||
@@ -19,15 +19,17 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
|
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
|
||||||
{
|
{
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
|
readonly AsyncEvent<InterceptingPublishMessageEventArgs> _interceptingPublishMessageEvent = new AsyncEvent<InterceptingPublishMessageEventArgs>();
|
||||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||||
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
|
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
|
||||||
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
|
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||||
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
|
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
|
||||||
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
|
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
|
||||||
|
readonly AsyncEvent<SubscriptionsChangedEventArgs> _subscriptionsChangedEvent = new AsyncEvent<SubscriptionsChangedEventArgs>();
|
||||||
|
|
||||||
readonly MqttNetSourceLogger _logger;
|
|
||||||
readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
|
readonly BlockingQueue<ManagedMqttApplicationMessage> _messageQueue = new BlockingQueue<ManagedMqttApplicationMessage>();
|
||||||
|
|
||||||
readonly AsyncLock _messageQueueLock = new AsyncLock();
|
readonly AsyncLock _messageQueueLock = new AsyncLock();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -88,6 +90,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
|
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Func<InterceptingPublishMessageEventArgs, Task> InterceptPublishMessageAsync
|
||||||
|
{
|
||||||
|
add => _interceptingPublishMessageEvent.AddHandler(value);
|
||||||
|
remove => _interceptingPublishMessageEvent.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||||
{
|
{
|
||||||
add => InternalClient.ApplicationMessageReceivedAsync += value;
|
add => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||||
@@ -124,6 +132,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
|
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Func<SubscriptionsChangedEventArgs, Task> SubscriptionsChangedAsync
|
||||||
|
{
|
||||||
|
add => _subscriptionsChangedEvent.AddHandler(value);
|
||||||
|
remove => _subscriptionsChangedEvent.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
public IMqttClient InternalClient { get; }
|
public IMqttClient InternalClient { get; }
|
||||||
|
|
||||||
public bool IsConnected => InternalClient.IsConnected;
|
public bool IsConnected => InternalClient.IsConnected;
|
||||||
@@ -353,13 +367,28 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
|
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task HandleSubscriptionExceptionAsync(Exception exception)
|
CancellationTokenSource NewTimeoutToken(CancellationToken linkedToken)
|
||||||
|
{
|
||||||
|
var newTimeoutToken = CancellationTokenSource.CreateLinkedTokenSource(linkedToken);
|
||||||
|
newTimeoutToken.CancelAfter(Options.ClientOptions.Timeout);
|
||||||
|
return newTimeoutToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
||||||
|
|
||||||
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
|
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
|
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleSubscriptionsResultAsync(SendSubscribeUnsubscribeResult subscribeUnsubscribeResult)
|
||||||
|
{
|
||||||
|
if (_subscriptionsChangedEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
await _subscriptionsChangedEvent.InvokeAsync(new SubscriptionsChangedEventArgs(subscribeUnsubscribeResult.SubscribeResults, subscribeUnsubscribeResult.UnsubscribeResults)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +416,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
if (_isCleanDisconnect)
|
if (_isCleanDisconnect)
|
||||||
{
|
{
|
||||||
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
using (var disconnectTimeout = NewTimeoutToken(CancellationToken.None))
|
||||||
{
|
{
|
||||||
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -437,7 +466,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
|
await TryPublishQueuedMessageAsync(message, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -453,15 +482,18 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task PublishReconnectSubscriptionsAsync()
|
async Task PublishReconnectSubscriptionsAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Info("Publishing subscriptions at reconnect");
|
_logger.Info("Publishing subscriptions at reconnect");
|
||||||
|
|
||||||
|
List<MqttTopicFilter> topicFilters = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_reconnectSubscriptions.Any())
|
if (_reconnectSubscriptions.Any())
|
||||||
{
|
{
|
||||||
var topicFilters = new List<MqttTopicFilter>();
|
topicFilters = new List<MqttTopicFilter>();
|
||||||
|
SendSubscribeUnsubscribeResult subscribeUnsubscribeResult;
|
||||||
|
|
||||||
foreach (var sub in _reconnectSubscriptions)
|
foreach (var sub in _reconnectSubscriptions)
|
||||||
{
|
{
|
||||||
@@ -469,17 +501,19 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||||
{
|
{
|
||||||
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
|
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(topicFilters, null, cancellationToken).ConfigureAwait(false);
|
||||||
topicFilters.Clear();
|
topicFilters.Clear();
|
||||||
|
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendSubscribeUnsubscribe(topicFilters, null).ConfigureAwait(false);
|
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(topicFilters, null, cancellationToken).ConfigureAwait(false);
|
||||||
|
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,6 +524,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
|
while (await _subscriptionsQueuedSignal.WaitAsync(GetRemainingTime(endTime), cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
List<MqttTopicFilter> subscriptions;
|
List<MqttTopicFilter> subscriptions;
|
||||||
|
SendSubscribeUnsubscribeResult subscribeUnsubscribeResult;
|
||||||
HashSet<string> unsubscriptions;
|
HashSet<string> unsubscriptions;
|
||||||
|
|
||||||
lock (_subscriptions)
|
lock (_subscriptions)
|
||||||
@@ -525,12 +560,14 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
if (addedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
if (addedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||||
{
|
{
|
||||||
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
|
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(addedTopicFilters, null, cancellationToken).ConfigureAwait(false);
|
||||||
addedTopicFilters.Clear();
|
addedTopicFilters.Clear();
|
||||||
|
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendSubscribeUnsubscribe(addedTopicFilters, null).ConfigureAwait(false);
|
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(addedTopicFilters, null, cancellationToken).ConfigureAwait(false);
|
||||||
|
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
|
||||||
|
|
||||||
var removedTopicFilters = new List<string>();
|
var removedTopicFilters = new List<string>();
|
||||||
foreach (var unSub in unsubscriptions)
|
foreach (var unSub in unsubscriptions)
|
||||||
@@ -539,12 +576,14 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
if (removedTopicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||||
{
|
{
|
||||||
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
|
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(null, removedTopicFilters, cancellationToken).ConfigureAwait(false);
|
||||||
removedTopicFilters.Clear();
|
removedTopicFilters.Clear();
|
||||||
|
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendSubscribeUnsubscribe(null, removedTopicFilters).ConfigureAwait(false);
|
subscribeUnsubscribeResult = await SendSubscribeUnsubscribe(null, removedTopicFilters, cancellationToken).ConfigureAwait(false);
|
||||||
|
await HandleSubscriptionsResultAsync(subscribeUnsubscribeResult).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +597,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
MqttClientConnectResult connectResult = null;
|
MqttClientConnectResult connectResult = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var connectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
using (var connectTimeout = NewTimeoutToken(cancellationToken))
|
||||||
{
|
{
|
||||||
connectResult = await InternalClient.ConnectAsync(Options.ClientOptions, connectTimeout.Token).ConfigureAwait(false);
|
connectResult = await InternalClient.ConnectAsync(Options.ClientOptions, connectTimeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -577,8 +616,10 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
async Task<SendSubscribeUnsubscribeResult> SendSubscribeUnsubscribe(List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var subscribeResults = new List<MqttClientSubscribeResult>();
|
||||||
|
var unsubscribeResults = new List<MqttClientUnsubscribeResult>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (removedSubscriptions != null && removedSubscriptions.Any())
|
if (removedSubscriptions != null && removedSubscriptions.Any())
|
||||||
@@ -590,7 +631,14 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
unsubscribeOptionsBuilder.WithTopicFilter(removedSubscription);
|
unsubscribeOptionsBuilder.WithTopicFilter(removedSubscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
using (var unsubscribeTimeout = NewTimeoutToken(cancellationToken))
|
||||||
|
{
|
||||||
|
var unsubscribeResult = await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build(), unsubscribeTimeout.Token).ConfigureAwait(false);
|
||||||
|
unsubscribeResults.Add(unsubscribeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
//clear because these worked, maybe the subscribe below will fail, only report those
|
||||||
|
removedSubscriptions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addedSubscriptions != null && addedSubscriptions.Any())
|
if (addedSubscriptions != null && addedSubscriptions.Any())
|
||||||
@@ -602,13 +650,19 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
subscribeOptionsBuilder.WithTopicFilter(addedSubscription);
|
subscribeOptionsBuilder.WithTopicFilter(addedSubscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
using (var subscribeTimeout = NewTimeoutToken(cancellationToken))
|
||||||
|
{
|
||||||
|
var subscribeResult = await InternalClient.SubscribeAsync(subscribeOptionsBuilder.Build(), subscribeTimeout.Token).ConfigureAwait(false);
|
||||||
|
subscribeResults.Add(subscribeResult);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new SendSubscribeUnsubscribeResult(subscribeResults, unsubscribeResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartPublishing()
|
void StartPublishing()
|
||||||
@@ -662,7 +716,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
else if (connectionState == ReconnectionResult.Reconnected)
|
else if (connectionState == ReconnectionResult.Reconnected)
|
||||||
{
|
{
|
||||||
await PublishReconnectSubscriptionsAsync().ConfigureAwait(false);
|
await PublishReconnectSubscriptionsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
StartPublishing();
|
StartPublishing();
|
||||||
}
|
}
|
||||||
else if (connectionState == ReconnectionResult.Recovered)
|
else if (connectionState == ReconnectionResult.Recovered)
|
||||||
@@ -692,12 +746,26 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
|
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Exception transmitException = null;
|
Exception transmitException = null;
|
||||||
|
bool acceptPublish = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
|
if (_interceptingPublishMessageEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
var interceptEventArgs = new InterceptingPublishMessageEventArgs(message);
|
||||||
|
await _interceptingPublishMessageEvent.InvokeAsync(interceptEventArgs).ConfigureAwait(false);
|
||||||
|
acceptPublish = interceptEventArgs.AcceptPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptPublish)
|
||||||
|
{
|
||||||
|
using (var publishTimeout = NewTimeoutToken(cancellationToken))
|
||||||
|
{
|
||||||
|
await InternalClient.PublishAsync(message.ApplicationMessage, publishTimeout.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
|
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
|
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
|
||||||
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
|
/// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,33 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Packets;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.ManagedClient
|
namespace MQTTnet.Extensions.ManagedClient
|
||||||
{
|
{
|
||||||
public class ManagedProcessFailedEventArgs : EventArgs
|
public class ManagedProcessFailedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public ManagedProcessFailedEventArgs(Exception exception)
|
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||||
{
|
{
|
||||||
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||||
|
|
||||||
|
if (addedSubscriptions != null)
|
||||||
|
{
|
||||||
|
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedSubscriptions != null)
|
||||||
|
{
|
||||||
|
RemovedSubscriptions = new List<string>(removedSubscriptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Exception Exception { get; }
|
public Exception Exception { get; }
|
||||||
|
|
||||||
|
public List<string> AddedSubscriptions { get; }
|
||||||
|
public List<string> RemovedSubscriptions { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,10 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||||
{
|
{
|
||||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
if (factory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(factory));
|
||||||
|
}
|
||||||
|
|
||||||
if (mqttClient == null)
|
if (mqttClient == null)
|
||||||
{
|
{
|
||||||
@@ -24,10 +27,22 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
if (factory == null)
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
{
|
||||||
|
throw new ArgumentNullException(nameof(factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
|
||||||
|
{
|
||||||
|
return new ManagedMqttClientOptionsBuilder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
|
||||||
|
namespace MQTTnet.Extensions.ManagedClient
|
||||||
|
{
|
||||||
|
public sealed class SendSubscribeUnsubscribeResult
|
||||||
|
{
|
||||||
|
public SendSubscribeUnsubscribeResult(List<MqttClientSubscribeResult> subscribeResults, List<MqttClientUnsubscribeResult> unsubscribeResults)
|
||||||
|
{
|
||||||
|
SubscribeResults = subscribeResults ?? throw new ArgumentNullException(nameof(subscribeResults));
|
||||||
|
UnsubscribeResults = unsubscribeResults ?? throw new ArgumentNullException(nameof(unsubscribeResults));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MqttClientSubscribeResult> SubscribeResults { get; private set; }
|
||||||
|
|
||||||
|
public List<MqttClientUnsubscribeResult> UnsubscribeResults { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MQTTnet.Extensions.ManagedClient
|
||||||
|
{
|
||||||
|
public sealed class SubscriptionsChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public SubscriptionsChangedEventArgs(List<MqttClientSubscribeResult> subscribeResult, List<MqttClientUnsubscribeResult> unsubscribeResult)
|
||||||
|
{
|
||||||
|
SubscribeResult = subscribeResult ?? throw new ArgumentNullException(nameof(subscribeResult));
|
||||||
|
UnsubscribeResult = unsubscribeResult ?? throw new ArgumentNullException(nameof(unsubscribeResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MqttClientSubscribeResult> SubscribeResult { get; }
|
||||||
|
|
||||||
|
public List<MqttClientUnsubscribeResult> UnsubscribeResult { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -7,8 +12,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public interface IMqttRpcClient : IDisposable
|
public interface IMqttRpcClient : IDisposable
|
||||||
{
|
{
|
||||||
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
|
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
|
||||||
|
|
||||||
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default);
|
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -41,13 +43,13 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
_waitingCalls.Clear();
|
_waitingCalls.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
|
||||||
{
|
{
|
||||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false);
|
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException exception)
|
catch (OperationCanceledException exception)
|
||||||
{
|
{
|
||||||
@@ -61,14 +63,14 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (methodName == null)
|
if (methodName == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(methodName));
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
|
||||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||||
|
|
||||||
var requestTopic = topicNames.RequestTopic;
|
var requestTopic = topicNames.RequestTopic;
|
||||||
@@ -119,7 +121,6 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_waitingCalls.TryRemove(responseTopic, out _);
|
_waitingCalls.TryRemove(responseTopic, out _);
|
||||||
|
|
||||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +132,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
|
||||||
|
awaitable.TrySetResult(payloadBuffer);
|
||||||
|
|
||||||
// Set this message to handled to that other code can avoid execution etc.
|
// Set this message to handled to that other code can avoid execution etc.
|
||||||
eventArgs.IsHandled = true;
|
eventArgs.IsHandled = true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -11,13 +12,13 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public static class MqttRpcClientExtensions
|
public static class MqttRpcClientExtensions
|
||||||
{
|
{
|
||||||
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
|
||||||
{
|
{
|
||||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
|
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
|
||||||
|
|
||||||
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel);
|
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -10,9 +11,11 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public sealed class TopicGenerationContext
|
public sealed class TopicGenerationContext
|
||||||
{
|
{
|
||||||
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
|
||||||
|
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||||
|
Parameters = parameters;
|
||||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
@@ -20,6 +23,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
|
|
||||||
public string MethodName { get; }
|
public string MethodName { get; }
|
||||||
|
|
||||||
|
public IDictionary<string, object> Parameters { get; }
|
||||||
|
|
||||||
public IMqttClient MqttClient { get; }
|
public IMqttClient MqttClient { get; }
|
||||||
|
|
||||||
public MqttRpcClientOptions Options { get; }
|
public MqttRpcClientOptions Options { get; }
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
|
<AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName>
|
||||||
|
<RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<Company>The contributors of MQTTnet</Company>
|
||||||
|
<Product>MQTTnet</Product>
|
||||||
|
<Description>
|
||||||
|
This is an extension library which provides mqtt topic templating logic to support dispatch,
|
||||||
|
routing and similar functionality based on the well known moustache syntax, aka
|
||||||
|
AsyncAPI dynamic channel address.
|
||||||
|
</Description>
|
||||||
|
<Authors>The contributors of MQTTnet</Authors>
|
||||||
|
<PackageId>MQTTnet.Extensions.TopicTemplate</PackageId>
|
||||||
|
<SignAssembly>false</SignAssembly>
|
||||||
|
<DelaySign>false</DelaySign>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PackageTags>MQTT Message Queue Telemetry MQTTClient Messaging Routing AsyncAPI Template</PackageTags>
|
||||||
|
<NeutralLanguage>en-US</NeutralLanguage>
|
||||||
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||||
|
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
|
||||||
|
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||||
|
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||||
|
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
|
||||||
|
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
|
||||||
|
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||||
|
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
|
||||||
|
<DefaultLanguage>en</DefaultLanguage>
|
||||||
|
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\..\Images\nuget.png">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Extensions.TopicTemplate
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A topic template is an MQTT topic filter string that may contain
|
||||||
|
/// segments in curly braces called parameters. This well-known
|
||||||
|
/// 'moustache' syntax also matches AsyncAPI Channel Address Expressions.
|
||||||
|
/// The topic template is designed to support dynamic subscription/publication,
|
||||||
|
/// message-topic matching and routing. It is intended to be more convenient
|
||||||
|
/// than String.Format() for aforementioned purposes.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// topic/subtopic/{parameter}/{otherParameter}
|
||||||
|
/// </example>
|
||||||
|
public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||||
|
{
|
||||||
|
static readonly Regex MoustacheRegex = new Regex("{([^/]+?)}", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
readonly string[] _parameterSegments;
|
||||||
|
|
||||||
|
string _topicFilter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a topic template from an mqtt topic filter with moustache placeholders.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="topicTemplate">
|
||||||
|
/// </param>
|
||||||
|
/// <exception
|
||||||
|
/// cref="ArgumentNullException">
|
||||||
|
/// </exception>
|
||||||
|
public MqttTopicTemplate(string topicTemplate)
|
||||||
|
{
|
||||||
|
if (topicTemplate == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(topicTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate);
|
||||||
|
|
||||||
|
Template = topicTemplate;
|
||||||
|
_parameterSegments = topicTemplate.Split(MqttTopicFilterComparer.LevelSeparator)
|
||||||
|
.Select(segment => MoustacheRegex.Match(segment).Groups[1].Value)
|
||||||
|
.Select(s => s.Length > 0 ? s : null)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yield the template parameter names.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> Parameters => _parameterSegments.Where(s => s != null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The topic template string representation, e.g. A/B/{foo}/D.
|
||||||
|
/// </summary>
|
||||||
|
public string Template { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The topic template as an MQTT topic filter (+ substituted for all parameters). If the template
|
||||||
|
/// ends with a multi-level wildcard (hash), this will be reflected here.
|
||||||
|
/// </summary>
|
||||||
|
public string TopicFilter
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
LazyInitializer.EnsureInitialized(ref _topicFilter, () => MoustacheRegex.Replace(Template, MqttTopicFilterComparer.SingleLevelWildcard.ToString()));
|
||||||
|
return _topicFilter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the topic filter of this template, ending with a multi-level wildcard (hash).
|
||||||
|
/// </summary>
|
||||||
|
public string TopicTreeRootFilter
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var filter = TopicFilter;
|
||||||
|
// append slash if neccessary
|
||||||
|
if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) &&
|
||||||
|
!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString()))
|
||||||
|
{
|
||||||
|
filter += MqttTopicFilterComparer.LevelSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append hash if neccessary
|
||||||
|
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString()))
|
||||||
|
{
|
||||||
|
filter += MqttTopicFilterComparer.MultiLevelWildcard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(MqttTopicTemplate other)
|
||||||
|
{
|
||||||
|
return other != null && Template == other.Template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, obj))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, obj))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.GetType() != GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Equals((MqttTopicTemplate)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine the shortest common prefix of the given templates. Partial segments
|
||||||
|
/// are not returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="templates">
|
||||||
|
/// topic templates
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static MqttTopicTemplate FindCanonicalPrefix(IEnumerable<MqttTopicTemplate> templates)
|
||||||
|
{
|
||||||
|
string root = null;
|
||||||
|
|
||||||
|
string CommonPrefix(string a, string b)
|
||||||
|
{
|
||||||
|
var maxIndex = Math.Min(a.Length, b.Length) - 1;
|
||||||
|
for (var i = 0; i <= maxIndex; i++)
|
||||||
|
{
|
||||||
|
if (a[i] != b[i])
|
||||||
|
{
|
||||||
|
return a.Substring(0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Substring(0, maxIndex+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string topic in from template in templates select template.Template)
|
||||||
|
{
|
||||||
|
root = root == null ? topic : CommonPrefix(root, topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(root))
|
||||||
|
return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString());
|
||||||
|
|
||||||
|
if (root.Contains(MqttTopicFilterComparer.LevelSeparator) &&
|
||||||
|
!root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) &&
|
||||||
|
!root.EndsWith("}"))
|
||||||
|
{
|
||||||
|
root = root.Substring(0, root.LastIndexOf(MqttTopicFilterComparer.LevelSeparator)+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()))
|
||||||
|
root += MqttTopicFilterComparer.SingleLevelWildcard;
|
||||||
|
|
||||||
|
return new MqttTopicTemplate(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Template.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if this topic template matches a given topic.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="topic">
|
||||||
|
/// a fully specified topic
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="subtree">
|
||||||
|
/// true to match including the subtree (multi-level wildcard)
|
||||||
|
/// </param>
|
||||||
|
/// <returns>true iff the topic matches the template's filter</returns>
|
||||||
|
/// <exception
|
||||||
|
/// cref="InvalidOperationException">
|
||||||
|
/// </exception>
|
||||||
|
/// <exception
|
||||||
|
/// cref="ArgumentException">
|
||||||
|
/// if the topic is invalid
|
||||||
|
/// </exception>
|
||||||
|
public bool MatchesTopic(string topic, bool subtree = false)
|
||||||
|
{
|
||||||
|
var comparison = MqttTopicFilterComparer.Compare(topic, subtree ? TopicTreeRootFilter : TopicFilter);
|
||||||
|
if (comparison == MqttTopicFilterCompareResult.FilterInvalid)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Invalid filter");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparison == MqttTopicFilterCompareResult.TopicInvalid)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid topic", nameof(topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
return comparison == MqttTopicFilterCompareResult.IsMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract the parameter values from a topic corresponding to the template
|
||||||
|
/// parameters. The topic has to match this template.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="topic">
|
||||||
|
/// the topic
|
||||||
|
/// </param>
|
||||||
|
/// <returns>an enumeration of (parameter, index, value)</returns>
|
||||||
|
public IEnumerable<(string parameter, int index, string value)> ParseParameterValues(string topic)
|
||||||
|
{
|
||||||
|
if (!MatchesTopic(topic))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("the topic has to match this template", nameof(topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseParameterValuesInternal(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract the parameter values from the message topic corresponding to the template
|
||||||
|
/// parameters. The message topic has to match this topic template.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="message">
|
||||||
|
/// the message
|
||||||
|
/// </param>
|
||||||
|
/// <returns>an enumeration of (parameter, index, value)</returns>
|
||||||
|
public IEnumerable<(string parameter, int index, string value)> ParseParameterValues(MqttApplicationMessage message)
|
||||||
|
{
|
||||||
|
return ParseParameterValues(message.Topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to set a parameter to a given value. If the parameter is not present,
|
||||||
|
/// this is returned. The value must not contain slashes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="parameter">
|
||||||
|
/// a template parameter
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="value">
|
||||||
|
/// a string
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MqttTopicTemplate TrySetParameter(string parameter, string value)
|
||||||
|
{
|
||||||
|
if (parameter != null && _parameterSegments.Contains(parameter))
|
||||||
|
{
|
||||||
|
return WithParameter(parameter, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replace the given parameter with a single-level wildcard (plus sign).
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="parameter">
|
||||||
|
/// parameter name
|
||||||
|
/// </param>
|
||||||
|
/// <returns>the topic template (without the parameter)</returns>
|
||||||
|
public MqttTopicTemplate WithoutParameter(string parameter)
|
||||||
|
{
|
||||||
|
return WithParameter(parameter, MqttTopicFilterComparer.SingleLevelWildcard.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Substitute a parameter with a given value, thus removing the parameter. If the parameter is not present,
|
||||||
|
/// the method trows. The value must not contain slashes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="parameter">
|
||||||
|
/// a template parameter
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="value">
|
||||||
|
/// a string
|
||||||
|
/// </param>
|
||||||
|
/// <exception
|
||||||
|
/// cref="ArgumentException">
|
||||||
|
/// when the parameter is not present
|
||||||
|
/// </exception>
|
||||||
|
/// <returns>the topic template (without the parameter)</returns>
|
||||||
|
public MqttTopicTemplate WithParameter(string parameter, string value)
|
||||||
|
{
|
||||||
|
if (value == null || string.IsNullOrEmpty(parameter) || !_parameterSegments.Contains(parameter) || value.Contains(MqttTopicFilterComparer.LevelSeparator) ||
|
||||||
|
value.Contains(MqttTopicFilterComparer.MultiLevelWildcard))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("parameter must exist and value must not contain slashes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var moustache = "{" + parameter + "}";
|
||||||
|
return new MqttTopicTemplate(Template.Replace(moustache, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reuse parameters as they are extracted using another topic template on this template
|
||||||
|
/// when the parameter name matches. Useful
|
||||||
|
/// for compatibility routing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="parameters">
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MqttTopicTemplate WithParameterValuesFrom(IEnumerable<(string parameter, int index, string value)> parameters)
|
||||||
|
{
|
||||||
|
return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<(string parameter, int index, string value)> parseParameterValuesInternal(string topic)
|
||||||
|
{
|
||||||
|
// because we have a match, we know the segment array is at least the template's length
|
||||||
|
var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator);
|
||||||
|
for (var i = 0; i < _parameterSegments.Length; i++)
|
||||||
|
{
|
||||||
|
var name = _parameterSegments[i];
|
||||||
|
if (name != null)
|
||||||
|
{
|
||||||
|
yield return (name, i, segments[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Extensions.TopicTemplate
|
||||||
|
{
|
||||||
|
public static class TopicTemplateExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Modify this message builder to respond to a given message. The
|
||||||
|
/// message's response topic and correlation data are included
|
||||||
|
/// in the message builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="builder">
|
||||||
|
/// a message builder
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="message">
|
||||||
|
/// a message with a response topic
|
||||||
|
/// </param>
|
||||||
|
/// <returns>a message builder</returns>
|
||||||
|
/// <see
|
||||||
|
/// cref="BuildResponse" />
|
||||||
|
public static MqttApplicationMessageBuilder AsResponseTo(this MqttApplicationMessageBuilder builder, MqttApplicationMessage message)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(message.ResponseTopic))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("message does not have a response topic");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.WithTopic(message.ResponseTopic).WithCorrelationData(message.CorrelationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the filter topic according to the template, with
|
||||||
|
/// remaining template parameters substituted by single-level
|
||||||
|
/// wildcard.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="topicTemplate">
|
||||||
|
/// a topic template
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="subscribeTreeRoot">
|
||||||
|
/// whether to subscribe to the whole topic tree
|
||||||
|
/// </param>
|
||||||
|
/// <returns>the modified topic filter</returns>
|
||||||
|
public static MqttTopicFilterBuilder BuildFilter(this MqttTopicTemplate topicTemplate, bool subscribeTreeRoot = false)
|
||||||
|
{
|
||||||
|
return new MqttTopicFilterBuilder().WithTopicTemplate(topicTemplate, subscribeTreeRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return a message builder to respond to this message. The
|
||||||
|
/// message's response topic and correlation data are included
|
||||||
|
/// in the response message builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="message">
|
||||||
|
/// a message with a response topic
|
||||||
|
/// </param>
|
||||||
|
/// <returns>a message builder</returns>
|
||||||
|
/// <see
|
||||||
|
/// cref="AsResponseTo" />
|
||||||
|
public static MqttApplicationMessageBuilder BuildResponse(this MqttApplicationMessage message)
|
||||||
|
{
|
||||||
|
return new MqttApplicationMessageBuilder().AsResponseTo(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return whether the message matches the given topic template.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="message">
|
||||||
|
/// a message
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="topicTemplate">
|
||||||
|
/// a topic template
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="subtree">
|
||||||
|
/// whether to include the topic subtree
|
||||||
|
/// </param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool MatchesTopicTemplate(this MqttApplicationMessage message, MqttTopicTemplate topicTemplate, bool subtree = false)
|
||||||
|
{
|
||||||
|
return topicTemplate.MatchesTopic(message.Topic, subtree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the filter topic according to the template, with
|
||||||
|
/// template parameters substituted by a single-level
|
||||||
|
/// wildcard.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="builder">
|
||||||
|
/// a filter builder
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="topicTemplate">
|
||||||
|
/// a topic template
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="subscribeTreeRoot">
|
||||||
|
/// whether to subscribe to the whole topic tree
|
||||||
|
/// </param>
|
||||||
|
/// <returns>the modified topic filter</returns>
|
||||||
|
public static MqttTopicFilterBuilder WithTopicTemplate(this MqttTopicFilterBuilder builder, MqttTopicTemplate topicTemplate, bool subscribeTreeRoot = false)
|
||||||
|
{
|
||||||
|
return builder.WithTopic(subscribeTreeRoot ? topicTemplate.TopicTreeRootFilter : topicTemplate.TopicFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the publication topic according to the topic template. The template
|
||||||
|
/// must not have remaining (unset) parameters or contain wildcards.
|
||||||
|
/// </summary>
|
||||||
|
/// <param
|
||||||
|
/// name="builder">
|
||||||
|
/// a message builder
|
||||||
|
/// </param>
|
||||||
|
/// <param
|
||||||
|
/// name="topicTemplate">
|
||||||
|
/// a parameterless topic template
|
||||||
|
/// </param>
|
||||||
|
/// <returns>the modified message builder</returns>
|
||||||
|
/// <exception
|
||||||
|
/// cref="ArgumentException">
|
||||||
|
/// if the topic template has parameters
|
||||||
|
/// </exception>
|
||||||
|
public static MqttApplicationMessageBuilder WithTopicTemplate(this MqttApplicationMessageBuilder builder, MqttTopicTemplate topicTemplate)
|
||||||
|
{
|
||||||
|
if (topicTemplate.Parameters.Any())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("topic templates must be parameter-less when sending " + topicTemplate.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttTopicValidator.ThrowIfInvalid(topicTemplate.Template);
|
||||||
|
return builder.WithTopic(topicTemplate.Template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
var uri = _webSocketOptions.Uri;
|
var uri = _webSocketOptions.Uri;
|
||||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (_webSocketOptions.TlsOptions?.UseTls == false)
|
if (_webSocketOptions.TlsOptions?.UseTls == true)
|
||||||
{
|
{
|
||||||
uri = "ws://" + uri;
|
uri = "wss://" + uri;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uri = "wss://" + uri;
|
uri = "ws://" + uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
List<KeyValuePair<string, string>> customHeaders = null;
|
List<KeyValuePair<string, string>> customHeaders = null;
|
||||||
if (_webSocketOptions.RequestHeaders != null)
|
if (_webSocketOptions.RequestHeaders != null)
|
||||||
{
|
{
|
||||||
customHeaders = _webSocketOptions.RequestHeaders.Select(i => new KeyValuePair<string, string>(i.Key, i.Value)).ToList();
|
customHeaders = _webSocketOptions.RequestHeaders.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
EndPoint proxy = null;
|
EndPoint proxy = null;
|
||||||
@@ -92,18 +92,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
var webSocketVersion = WebSocketVersion.None;
|
var webSocketVersion = WebSocketVersion.None;
|
||||||
var receiveBufferSize = 0;
|
var receiveBufferSize = 0;
|
||||||
|
|
||||||
var certificates = new X509CertificateCollection();
|
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
|
||||||
if (_webSocketOptions.TlsOptions?.Certificates != null)
|
|
||||||
{
|
|
||||||
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
|
||||||
{
|
|
||||||
#if WINDOWS_UWP
|
|
||||||
certificates.Add(new X509Certificate(certificate));
|
|
||||||
#else
|
|
||||||
certificates.Add(certificate);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
|
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new MqttTcpChannel(options),
|
new MqttTcpChannel(options),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
logger)
|
||||||
logger);
|
{
|
||||||
|
PacketInspector = packetInspector
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case MqttClientWebSocketOptions webSocketOptions:
|
case MqttClientWebSocketOptions webSocketOptions:
|
||||||
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
logger)
|
||||||
logger);
|
{
|
||||||
|
PacketInspector = packetInspector
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
@@ -27,15 +28,24 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "127.0.0.1"
|
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client.ApplicationMessageReceivedAsync += e =>
|
client.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
|
var payloadText = string.Empty;
|
||||||
|
if (e.ApplicationMessage.PayloadSegment.Count > 0)
|
||||||
|
{
|
||||||
|
payloadText = Encoding.UTF8.GetString(
|
||||||
|
e.ApplicationMessage.PayloadSegment.Array,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Offset,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Count);
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
||||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
||||||
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
|
Console.WriteLine($"+ Payload = {payloadText}");
|
||||||
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
||||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.ManagedClient;
|
using MQTTnet.Extensions.ManagedClient;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
@@ -28,7 +29,7 @@ namespace MQTTnet.TestApp
|
|||||||
Credentials = new RandomPassword(),
|
Credentials = new RandomPassword(),
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "broker.hivemq.com"
|
RemoteEndpoint = new DnsEndPoint("broker.hivemq.com", 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MqttClient = MQTTnet.Client.MqttClient;
|
|
||||||
|
|
||||||
namespace MQTTnet.TestApp
|
namespace MQTTnet.TestApp
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "127.0.0.1"
|
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
|
||||||
},
|
},
|
||||||
CleanSession = true
|
CleanSession = true
|
||||||
};
|
};
|
||||||
@@ -71,7 +71,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "127.0.0.1"
|
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +114,10 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
var options = new MqttClientOptions
|
var options = new MqttClientOptions
|
||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions { Server = "localhost" },
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
|
{
|
||||||
|
RemoteEndpoint = new DnsEndPoint("localhost", 0)
|
||||||
|
},
|
||||||
ClientId = "Client1",
|
ClientId = "Client1",
|
||||||
CleanSession = true
|
CleanSession = true
|
||||||
};
|
};
|
||||||
@@ -196,7 +199,7 @@ namespace MQTTnet.TestApp
|
|||||||
return new MqttApplicationMessage
|
return new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes(Payload),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -218,7 +221,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "127.0.0.1"
|
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
|
||||||
},
|
},
|
||||||
CleanSession = true
|
CleanSession = true
|
||||||
};
|
};
|
||||||
@@ -229,7 +232,7 @@ namespace MQTTnet.TestApp
|
|||||||
var message = new MqttApplicationMessage
|
var message = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -269,7 +272,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "127.0.0.1"
|
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
|
||||||
},
|
},
|
||||||
CleanSession = true
|
CleanSession = true
|
||||||
};
|
};
|
||||||
@@ -280,7 +283,7 @@ namespace MQTTnet.TestApp
|
|||||||
var message = new MqttApplicationMessage
|
var message = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -320,7 +323,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
ChannelOptions = new MqttClientTcpOptions
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
{
|
{
|
||||||
Server = "127.0.0.1"
|
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
|
||||||
},
|
},
|
||||||
CleanSession = true
|
CleanSession = true
|
||||||
};
|
};
|
||||||
@@ -331,7 +334,7 @@ namespace MQTTnet.TestApp
|
|||||||
var message = new MqttApplicationMessage
|
var message = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,143 +20,155 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
// TLS13 is only available in Net5.0
|
// TLS13 is only available in Net5.0
|
||||||
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls13 = new MqttClientTlsOptions
|
||||||
{
|
{
|
||||||
UseTls = true,
|
UseTls = true,
|
||||||
SslProtocol = SslProtocols.Tls13,
|
SslProtocol = SslProtocols.Tls13,
|
||||||
// Don't use this in production code. This handler simply allows any invalid certificate to work.
|
// Don't use this in production code. This handler simply allows any invalid certificate to work.
|
||||||
CertificateValidationHandler = w => true
|
AllowUntrustedCertificates = true,
|
||||||
|
IgnoreCertificateChainErrors = true,
|
||||||
|
CertificateValidationHandler = _ => true
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
||||||
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls12 = new MqttClientTlsOptions
|
||||||
{
|
{
|
||||||
UseTls = true,
|
UseTls = true,
|
||||||
SslProtocol = SslProtocols.Tls12,
|
SslProtocol = SslProtocols.Tls12,
|
||||||
// Don't use this in production code. This handler simply allows any invalid certificate to work.
|
// Don't use this in production code. This handler simply allows any invalid certificate to work.
|
||||||
CertificateValidationHandler = w => true
|
AllowUntrustedCertificates = true,
|
||||||
|
IgnoreCertificateChainErrors = true,
|
||||||
|
CertificateValidationHandler = _ => true
|
||||||
};
|
};
|
||||||
|
|
||||||
// mqtt.eclipseprojects.io
|
// mqtt.eclipseprojects.io
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"mqtt.eclipseprojects.io TCP",
|
"mqtt.eclipseprojects.io TCP",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"mqtt.eclipseprojects.io WS",
|
"mqtt.eclipseprojects.io WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
await ExecuteTestsAsync("mqtt.eclipseprojects.io WS TLS13",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
await ExecuteTestsAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
|
||||||
true);
|
true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// test.mosquitto.org
|
// test.mosquitto.org
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org TCP",
|
"test.mosquitto.org TCP",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org TCP - Authenticated",
|
"test.mosquitto.org TCP - Authenticated",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org TCP TLS12",
|
"test.mosquitto.org TCP TLS12",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
await ExecuteTestsAsync("test.mosquitto.org TCP TLS13",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org TCP TLS12 - Authenticated",
|
"test.mosquitto.org TCP TLS12 - Authenticated",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
||||||
.WithCredentials("rw", "readwrite")
|
.WithCredentials("rw", "readwrite")
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311)
|
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||||
.WithTls(unsafeTls12)
|
.WithTlsOptions(unsafeTls12)
|
||||||
.Build());
|
.Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org WS",
|
"test.mosquitto.org WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org WS (WebSocket4Net)",
|
"test.mosquitto.org WS (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"test.mosquitto.org WS TLS12",
|
"test.mosquitto.org WS TLS12",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
// await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
"test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
||||||
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
|
||||||
// true);
|
true);
|
||||||
|
|
||||||
// broker.emqx.io
|
// broker.emqx.io
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.emqx.io TCP",
|
"broker.emqx.io TCP",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.emqx.io TCP TLS12",
|
"broker.emqx.io TCP TLS12",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
await ExecuteTestsAsync("broker.emqx.io TCP TLS13",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.emqx.io WS",
|
"broker.emqx.io WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.emqx.io WS (WebSocket4Net)",
|
"broker.emqx.io WS (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.emqx.io WS TLS12",
|
"broker.emqx.io WS TLS12",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
// broker.hivemq.com
|
// broker.hivemq.com
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.hivemq.com TCP",
|
"broker.hivemq.com TCP",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.hivemq.com WS",
|
"broker.hivemq.com WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestsAsync(
|
||||||
"broker.hivemq.com WS (WebSocket4Net)",
|
"broker.hivemq.com WS (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
// mqtt.swifitch.cz: Does not seem to operate any more
|
// mqtt.swifitch.cz: Does not seem to operate any more
|
||||||
|
|
||||||
// cloudmqtt.com: Cannot test because it does not offer a free plan any more.
|
// cloudmqtt.com: Cannot test because it does not offer a free plan any more.
|
||||||
|
|
||||||
Write("Finished.", ConsoleColor.White);
|
Write("Finished.", ConsoleColor.White);
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async Task ExecuteTestsAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
|
||||||
|
{
|
||||||
|
options.ProtocolVersion = MqttProtocolVersion.V311;
|
||||||
|
await ExecuteTestAsync(name + " V3.1.1", options, useWebSocket4Net);
|
||||||
|
|
||||||
|
options.ProtocolVersion = MqttProtocolVersion.V500;
|
||||||
|
await ExecuteTestAsync(name + " V5.0.0", options, useWebSocket4Net);
|
||||||
|
}
|
||||||
|
|
||||||
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
|
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -197,9 +209,10 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
Write("[OK]\n", ConsoleColor.Green);
|
Write("[OK]\n", ConsoleColor.Green);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red);
|
Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red);
|
||||||
|
Write(exception + Environment.NewLine, ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
// Replace the payload with the timestamp. But also extending a JSON
|
// Replace the payload with the timestamp. But also extending a JSON
|
||||||
// based payload with the timestamp is a suitable use case.
|
// based payload with the timestamp is a suitable use case.
|
||||||
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
|
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.ApplicationMessage.Topic == "not_allowed_topic")
|
if (e.ApplicationMessage.Topic == "not_allowed_topic")
|
||||||
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
mqttServer.InterceptingPublishAsync += e =>
|
mqttServer.InterceptingPublishAsync += e =>
|
||||||
{
|
{
|
||||||
MqttNetConsoleLogger.PrintToConsole(
|
var payloadText = string.Empty;
|
||||||
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
|
if (e.ApplicationMessage.PayloadSegment.Count > 0)
|
||||||
ConsoleColor.Magenta);
|
{
|
||||||
|
payloadText = Encoding.UTF8.GetString(
|
||||||
|
e.ApplicationMessage.PayloadSegment.Array,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Offset,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using MQTTnet.Exceptions;
|
|||||||
using MQTTnet.LowLevelClient;
|
using MQTTnet.LowLevelClient;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using MQTTnet.Diagnostics;
|
|||||||
using MQTTnet.Extensions.ManagedClient;
|
using MQTTnet.Extensions.ManagedClient;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
@@ -22,67 +23,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
[TestMethod]
|
|
||||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
|
||||||
|
|
||||||
var senderClient = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
// Prepare managed client.
|
|
||||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
|
||||||
await managedClient.SubscribeAsync("#");
|
|
||||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
|
||||||
|
|
||||||
var managedClientOptions = new ManagedMqttClientOptions
|
|
||||||
{
|
|
||||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
|
||||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
|
||||||
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
|
||||||
.WithCleanSession(false)
|
|
||||||
.Build()
|
|
||||||
};
|
|
||||||
|
|
||||||
await managedClient.StartAsync(managedClientOptions);
|
|
||||||
await LongTestDelay();
|
|
||||||
await LongTestDelay();
|
|
||||||
|
|
||||||
// Send test data.
|
|
||||||
await senderClient.PublishStringAsync("topic1");
|
|
||||||
await LongTestDelay();
|
|
||||||
await LongTestDelay();
|
|
||||||
|
|
||||||
receivedMessages.AssertReceivedCountEquals(1);
|
|
||||||
|
|
||||||
// Stop the managed client but keep session at server (not clean disconnect required).
|
|
||||||
await managedClient.StopAsync(false);
|
|
||||||
await LongTestDelay();
|
|
||||||
|
|
||||||
// Send new messages in the meantime.
|
|
||||||
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
|
||||||
await LongTestDelay();
|
|
||||||
|
|
||||||
// Start the managed client, it should receive the new message.
|
|
||||||
await managedClient.StartAsync(managedClientOptions);
|
|
||||||
await LongTestDelay();
|
|
||||||
|
|
||||||
receivedMessages.AssertReceivedCountEquals(2);
|
|
||||||
|
|
||||||
// Stop and start again, no new message should be received.
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
await managedClient.StopAsync(false);
|
|
||||||
await LongTestDelay();
|
|
||||||
await managedClient.StartAsync(managedClientOptions);
|
|
||||||
await LongTestDelay();
|
|
||||||
}
|
|
||||||
|
|
||||||
receivedMessages.AssertReceivedCountEquals(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Connect_To_Invalid_Server()
|
public async Task Connect_To_Invalid_Server()
|
||||||
{
|
{
|
||||||
@@ -149,6 +89,38 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Expose_Custom_Connection_Error()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += args =>
|
||||||
|
{
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||||
|
|
||||||
|
MqttClientDisconnectedEventArgs disconnectedArgs = null;
|
||||||
|
managedClient.DisconnectedAsync += args =>
|
||||||
|
{
|
||||||
|
disconnectedArgs = args;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
|
||||||
|
await managedClient.StartAsync(clientOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.IsNotNull(disconnectedArgs);
|
||||||
|
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task ManagedClients_Will_Message_Send()
|
public async Task ManagedClients_Will_Message_Send()
|
||||||
{
|
{
|
||||||
@@ -192,6 +164,67 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||||
|
|
||||||
|
var senderClient = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
// Prepare managed client.
|
||||||
|
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||||
|
await managedClient.SubscribeAsync("#");
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
||||||
|
|
||||||
|
var managedClientOptions = new ManagedMqttClientOptions
|
||||||
|
{
|
||||||
|
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||||
|
.WithCleanSession(false)
|
||||||
|
.Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Send test data.
|
||||||
|
await senderClient.PublishStringAsync("topic1");
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
// Stop the managed client but keep session at server (not clean disconnect required).
|
||||||
|
await managedClient.StopAsync(false);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Send new messages in the meantime.
|
||||||
|
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Start the managed client, it should receive the new message.
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(2);
|
||||||
|
|
||||||
|
// Stop and start again, no new message should be received.
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
await managedClient.StopAsync(false);
|
||||||
|
await LongTestDelay();
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Start_Stop()
|
public async Task Start_Stop()
|
||||||
{
|
{
|
||||||
@@ -389,7 +422,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
|
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
|
||||||
var sendingClient = await testEnvironment.ConnectClient();
|
var sendingClient = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
|
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
|
||||||
|
|
||||||
var subscribeTime = DateTime.UtcNow;
|
var subscribeTime = DateTime.UtcNow;
|
||||||
|
|
||||||
@@ -405,6 +438,109 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ManagedClients_CanInterceptPublishedMessage_PreventingPublish()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
var interceptedMessagesCount = 0;
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var sendingClient = testEnvironment.CreateClient();
|
||||||
|
var sendingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(sendingClient, testEnvironment.ClientLogger);
|
||||||
|
|
||||||
|
await sendingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||||
|
|
||||||
|
// Wait until the managed client is fully set up and running.
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
var receivingClient = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
receivingClient.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
sendingManagedClient.InterceptPublishMessageAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref interceptedMessagesCount);
|
||||||
|
e.AcceptPublish = false;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await receivingClient.SubscribeAsync("My/last/will");
|
||||||
|
|
||||||
|
// Wait for arrival of the will message at the receiver.
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
await sendingManagedClient.EnqueueAsync("My/last/will", "hello world");
|
||||||
|
|
||||||
|
// Wait for arrival of the will message at the receiver.
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, receivedMessagesCount);
|
||||||
|
Assert.AreEqual(1, interceptedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ManagedClients_CanInterceptPublishedMessage_AllowingPublish()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
var interceptedMessagesCount = 0;
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder()
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var sendingClient = testEnvironment.CreateClient();
|
||||||
|
var sendingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(sendingClient, testEnvironment.ClientLogger);
|
||||||
|
|
||||||
|
await sendingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||||
|
|
||||||
|
// Wait until the managed client is fully set up and running.
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
var receivingClient = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
receivingClient.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
sendingManagedClient.InterceptPublishMessageAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref interceptedMessagesCount);
|
||||||
|
e.AcceptPublish = true;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await receivingClient.SubscribeAsync("My/last/will");
|
||||||
|
|
||||||
|
// Wait for arrival of the will message at the receiver.
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
await sendingManagedClient.EnqueueAsync("My/last/will", "hello world");
|
||||||
|
|
||||||
|
// Wait for arrival of the will message at the receiver.
|
||||||
|
await Task.Delay(5000);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, receivedMessagesCount);
|
||||||
|
Assert.AreEqual(1, interceptedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This case also serves as a regression test for the previous behavior which re-published
|
// This case also serves as a regression test for the previous behavior which re-published
|
||||||
// each and every existing subscriptions with every new subscription that was made
|
// each and every existing subscriptions with every new subscription that was made
|
||||||
// (causing performance problems and having the visible symptom of retained messages being received again)
|
// (causing performance problems and having the visible symptom of retained messages being received again)
|
||||||
@@ -422,7 +558,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
//wait a bit for the subscription to become established
|
//wait a bit for the subscription to become established
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
|
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
|
||||||
|
|
||||||
var messages = await SetupReceivingOfMessages(managedClient, 1);
|
var messages = await SetupReceivingOfMessages(managedClient, 1);
|
||||||
|
|
||||||
@@ -439,16 +575,154 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Subscribe_Does_Not_Hang_On_Server_Stop()
|
||||||
|
{
|
||||||
|
var timeout = TimeSpan.FromSeconds(2);
|
||||||
|
var testTimeout = TimeSpan.FromSeconds(timeout.TotalSeconds * 2);
|
||||||
|
const string topic = "test_topic_2";
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
using (var managedClient = await CreateManagedClientAsync(testEnvironment, timeout: timeout))
|
||||||
|
{
|
||||||
|
testEnvironment.IgnoreClientLogErrors = true;
|
||||||
|
bool reject = true;
|
||||||
|
var receivedOnServer = new SemaphoreSlim(0, 1);
|
||||||
|
var failedOnClient = new SemaphoreSlim(0, 1);
|
||||||
|
testEnvironment.Server.InterceptingInboundPacketAsync += e =>
|
||||||
|
{
|
||||||
|
if (e.Packet is MqttSubscribePacket)
|
||||||
|
{
|
||||||
|
if (reject)
|
||||||
|
{
|
||||||
|
e.ProcessPacket = false;
|
||||||
|
}
|
||||||
|
receivedOnServer.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
managedClient.SynchronizingSubscriptionsFailedAsync += e =>
|
||||||
|
{
|
||||||
|
failedOnClient.Release();
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await managedClient.SubscribeAsync(topic);
|
||||||
|
Assert.IsTrue(await receivedOnServer.WaitAsync(testTimeout));
|
||||||
|
Assert.IsTrue(await failedOnClient.WaitAsync(testTimeout));
|
||||||
|
|
||||||
|
reject = false;
|
||||||
|
await managedClient.SubscribeAsync(topic);
|
||||||
|
Assert.IsTrue(await receivedOnServer.WaitAsync(testTimeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Unsubscribe_Does_Not_Hang_On_Server_Stop()
|
||||||
|
{
|
||||||
|
var timeout = TimeSpan.FromSeconds(2);
|
||||||
|
var testTimeout = TimeSpan.FromSeconds(timeout.TotalSeconds * 2);
|
||||||
|
const string topic = "test_topic_2";
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
using (var managedClient = await CreateManagedClientAsync(testEnvironment, timeout: timeout))
|
||||||
|
{
|
||||||
|
testEnvironment.IgnoreClientLogErrors = true;
|
||||||
|
bool reject = true;
|
||||||
|
var receivedOnServer = new SemaphoreSlim(0, 1);
|
||||||
|
var failedOnClient = new SemaphoreSlim(0, 1);
|
||||||
|
testEnvironment.Server.InterceptingInboundPacketAsync += e =>
|
||||||
|
{
|
||||||
|
if (e.Packet is MqttUnsubscribePacket)
|
||||||
|
{
|
||||||
|
if (reject)
|
||||||
|
{
|
||||||
|
e.ProcessPacket = false;
|
||||||
|
}
|
||||||
|
receivedOnServer.Release();
|
||||||
|
}
|
||||||
|
else if (e.Packet is MqttSubscribePacket)
|
||||||
|
{
|
||||||
|
receivedOnServer.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
managedClient.SynchronizingSubscriptionsFailedAsync += e =>
|
||||||
|
{
|
||||||
|
failedOnClient.Release();
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await managedClient.SubscribeAsync(topic);
|
||||||
|
Assert.IsTrue(await receivedOnServer.WaitAsync(testTimeout));
|
||||||
|
|
||||||
|
await managedClient.UnsubscribeAsync(topic);
|
||||||
|
Assert.IsTrue(await receivedOnServer.WaitAsync(testTimeout));
|
||||||
|
Assert.IsTrue(await failedOnClient.WaitAsync(testTimeout));
|
||||||
|
|
||||||
|
reject = false;
|
||||||
|
await managedClient.UnsubscribeAsync(topic);
|
||||||
|
Assert.IsTrue(await receivedOnServer.WaitAsync(testTimeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Publish_Does_Not_Hang_On_Server_Error()
|
||||||
|
{
|
||||||
|
var timeout = TimeSpan.FromSeconds(2);
|
||||||
|
var testTimeout = TimeSpan.FromSeconds(timeout.TotalSeconds * 2);
|
||||||
|
|
||||||
|
const string topic = "test_topic_42";
|
||||||
|
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
using (var managedClient = await CreateManagedClientAsync(testEnvironment, timeout: timeout))
|
||||||
|
{
|
||||||
|
testEnvironment.IgnoreClientLogErrors = true;
|
||||||
|
bool reject = true;
|
||||||
|
var receivedOnServer = new TaskCompletionSource<object>();
|
||||||
|
managedClient.ApplicationMessageProcessedAsync += e => Task.FromResult(reject &= e.Exception is null);
|
||||||
|
testEnvironment.Server.InterceptingInboundPacketAsync += e =>
|
||||||
|
{
|
||||||
|
if (e.Packet is MqttPublishPacket)
|
||||||
|
{
|
||||||
|
if (reject)
|
||||||
|
{
|
||||||
|
e.ProcessPacket = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
receivedOnServer.TrySetResult(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await managedClient.EnqueueAsync(new MqttApplicationMessage { Topic = topic, PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
|
||||||
|
|
||||||
|
var timeoutTask = Task.Delay(testTimeout);
|
||||||
|
|
||||||
|
var firstDone = await Task.WhenAny(receivedOnServer.Task, timeoutTask);
|
||||||
|
Assert.AreEqual(receivedOnServer.Task, firstDone, "Client is hung on publish!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
|
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
|
||||||
TestEnvironment testEnvironment,
|
TestEnvironment testEnvironment,
|
||||||
IMqttClient underlyingClient = null,
|
IMqttClient underlyingClient = null,
|
||||||
TimeSpan? connectionCheckInterval = null,
|
TimeSpan? connectionCheckInterval = null,
|
||||||
string host = "localhost")
|
string host = "localhost",
|
||||||
|
TimeSpan? timeout = null)
|
||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer(host, testEnvironment.ServerPort);
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer(host, testEnvironment.ServerPort);
|
||||||
|
|
||||||
|
if (timeout != null)
|
||||||
|
{
|
||||||
|
clientOptions.WithTimeout(timeout.Value);
|
||||||
|
}
|
||||||
|
|
||||||
var managedOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build();
|
var managedOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build();
|
||||||
|
|
||||||
// Use a short connection check interval so that subscription operations are performed quickly
|
// Use a short connection check interval so that subscription operations are performed quickly
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -10,6 +11,7 @@ using MQTTnet.Client;
|
|||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build());
|
||||||
|
|
||||||
Assert.Fail("Must fail!");
|
Assert.Fail("Must fail!");
|
||||||
}
|
}
|
||||||
@@ -99,6 +101,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
// Perform a clean disconnect.
|
// Perform a clean disconnect.
|
||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||||
}
|
}
|
||||||
@@ -125,6 +129,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
// Perform a clean disconnect.
|
// Perform a clean disconnect.
|
||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||||
}
|
}
|
||||||
@@ -151,6 +157,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
// Perform a clean disconnect.
|
// Perform a clean disconnect.
|
||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.IsNotNull(eventArgs.UserProperties);
|
Assert.IsNotNull(eventArgs.UserProperties);
|
||||||
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
||||||
@@ -158,5 +166,89 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task No_Unobserved_Exception()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
testEnvironment.IgnoreClientLogErrors = true;
|
||||||
|
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
var options = new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(0.5)))
|
||||||
|
{
|
||||||
|
await client.ConnectAsync(options, timeout.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Dispose();
|
||||||
|
|
||||||
|
// These delays and GC calls are required in order to make calling the finalizer reproducible.
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_Non_Success()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += args =>
|
||||||
|
{
|
||||||
|
args.ResponseUserProperties = new List<MqttUserProperty>
|
||||||
|
{
|
||||||
|
new MqttUserProperty("Property", "Value")
|
||||||
|
};
|
||||||
|
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
|
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
|
||||||
|
|
||||||
|
Assert.IsNotNull(response);
|
||||||
|
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
|
||||||
|
Assert.AreEqual(response.UserProperties[0].Name, "Property");
|
||||||
|
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Throw_Proper_Exception_When_Not_Connected()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MqttClientNotConnectedException exception)
|
||||||
|
{
|
||||||
|
if (exception.Message == "The MQTT client is not connected.")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -17,6 +18,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
|
|
||||||
Assert.IsNotNull(receivedMessage);
|
Assert.IsNotNull(receivedMessage);
|
||||||
Assert.AreEqual("A", receivedMessage.Topic);
|
Assert.AreEqual("A", receivedMessage.Topic);
|
||||||
Assert.AreEqual(null, receivedMessage.Payload);
|
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +509,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
|
|
||||||
client2.ApplicationMessageReceivedAsync += e =>
|
client2.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
|
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -924,5 +926,54 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
Assert.IsFalse(disconnectedFired);
|
Assert.IsFalse(disconnectedFired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Backward_compatible_TCP_options()
|
||||||
|
{
|
||||||
|
var options = new MqttClientOptionsBuilder().WithTcpServer("host", 3).Build();
|
||||||
|
|
||||||
|
Assert.AreEqual("host", ((MqttClientTcpOptions)options.ChannelOptions).Server);
|
||||||
|
Assert.AreEqual(3, ((MqttClientTcpOptions)options.ChannelOptions).Port);
|
||||||
|
|
||||||
|
options = new MqttClientOptions
|
||||||
|
{
|
||||||
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
|
{
|
||||||
|
Server = "host",
|
||||||
|
Port = 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.AreEqual("host:3", options.ChannelOptions.ToString());
|
||||||
|
Assert.AreEqual("host", ((MqttClientTcpOptions)options.ChannelOptions).Server);
|
||||||
|
Assert.AreEqual(3, ((MqttClientTcpOptions)options.ChannelOptions).Port);
|
||||||
|
|
||||||
|
options = new MqttClientOptionsBuilder().WithEndPoint(new DnsEndPoint("host", 3)).Build();
|
||||||
|
Assert.AreEqual("Unspecified/host:3", options.ChannelOptions.ToString());
|
||||||
|
Assert.AreEqual("host", ((MqttClientTcpOptions)options.ChannelOptions).Server);
|
||||||
|
Assert.AreEqual(3, ((MqttClientTcpOptions)options.ChannelOptions).Port);
|
||||||
|
|
||||||
|
options = new MqttClientOptionsBuilder().WithTcpServer("host").Build();
|
||||||
|
|
||||||
|
Assert.AreEqual("host", ((MqttClientTcpOptions)options.ChannelOptions).Server);
|
||||||
|
Assert.AreEqual(1883, ((MqttClientTcpOptions)options.ChannelOptions).Port);
|
||||||
|
Assert.AreEqual("Unspecified/host:1883", options.ChannelOptions.ToString());
|
||||||
|
|
||||||
|
options = new MqttClientOptionsBuilder().WithTlsOptions(o => o.UseTls()).WithTcpServer("host").Build();
|
||||||
|
|
||||||
|
Assert.AreEqual("host", ((MqttClientTcpOptions)options.ChannelOptions).Server);
|
||||||
|
Assert.AreEqual(8883, ((MqttClientTcpOptions)options.ChannelOptions).Port);
|
||||||
|
|
||||||
|
options = new MqttClientOptions
|
||||||
|
{
|
||||||
|
ChannelOptions = new MqttClientTcpOptions
|
||||||
|
{
|
||||||
|
Server = "host"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.AreEqual("host", ((MqttClientTcpOptions)options.ChannelOptions).Server);
|
||||||
|
Assert.AreEqual(null, ((MqttClientTcpOptions)options.ChannelOptions).Port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
|
|||||||
|
|
||||||
var packets = new List<string>();
|
var packets = new List<string>();
|
||||||
|
|
||||||
mqttClient.InspectPackage += eventArgs =>
|
mqttClient.InspectPacketAsync += eventArgs =>
|
||||||
{
|
{
|
||||||
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
|
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class Extension_Tests
|
|
||||||
{
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfter()
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
|
|
||||||
TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterWithResult()
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(
|
|
||||||
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
|
|
||||||
TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterWithInnerException()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
|
|
||||||
{
|
|
||||||
var iis = new int[0];
|
|
||||||
iis[1] = 0;
|
|
||||||
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
|
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Assert.IsTrue(e is IndexOutOfRangeException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterWithInnerExceptionWithResult()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
|
|
||||||
{
|
|
||||||
var iis = new int[0];
|
|
||||||
iis[1] = 0;
|
|
||||||
return iis[0];
|
|
||||||
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
|
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Assert.IsTrue(e is IndexOutOfRangeException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterMemoryUsage()
|
|
||||||
{
|
|
||||||
var initialMemory = GC.GetTotalMemory(true);
|
|
||||||
|
|
||||||
var tasks = Enumerable.Range(0, 100000)
|
|
||||||
.Select(i =>
|
|
||||||
{
|
|
||||||
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
|
|
||||||
TimeSpan.FromMinutes(1), CancellationToken.None);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void AssertIsLess(long bound, long actual)
|
|
||||||
{
|
|
||||||
if (bound < actual)
|
|
||||||
{
|
|
||||||
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Extensions.TopicTemplate;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Extensions
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttTopicTemplate_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void BasicOps()
|
||||||
|
{
|
||||||
|
var template = new MqttTopicTemplate("A/B/{param}/D");
|
||||||
|
Assert.AreEqual("A/B/C/D", template.WithParameter("param", "C").Template);
|
||||||
|
Assert.AreEqual("A/B/+/D", template.WithoutParameter("param").Template);
|
||||||
|
Assert.AreEqual("A/B/+/D", template.TopicFilter);
|
||||||
|
Assert.AreEqual("A/B/+/D/#", template.TopicTreeRootFilter);
|
||||||
|
|
||||||
|
// corner cases
|
||||||
|
Assert.AreEqual("#", new MqttTopicTemplate("#").TopicFilter);
|
||||||
|
Assert.AreEqual("+", new MqttTopicTemplate("+").TopicFilter);
|
||||||
|
Assert.AreEqual("/", new MqttTopicTemplate("/").TopicFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BasicOpsWithHash()
|
||||||
|
{
|
||||||
|
var template = new MqttTopicTemplate("A/B/{param}/D/#");
|
||||||
|
Assert.AreEqual("A/B/C/D/#", template.WithParameter("param", "C").Template);
|
||||||
|
Assert.AreEqual("A/B/+/D/#", template.WithoutParameter("param").Template);
|
||||||
|
Assert.AreEqual("A/B/+/D/#", template.TopicFilter);
|
||||||
|
Assert.AreEqual("A/B/+/D/#", template.TopicTreeRootFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentException))]
|
||||||
|
public void RejectsReservedChars1()
|
||||||
|
{
|
||||||
|
var template = new MqttTopicTemplate("A/B/{foo}/D");
|
||||||
|
template.WithParameter("foo", "a#");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||||
|
public void RejectsEmptyTemplate()
|
||||||
|
{
|
||||||
|
var _ = new MqttTopicTemplate("");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentNullException))]
|
||||||
|
public void RejectsNullTemplate()
|
||||||
|
{
|
||||||
|
var _ = new MqttTopicTemplate(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentException))]
|
||||||
|
public void RejectsReservedChars2()
|
||||||
|
{
|
||||||
|
var template = new MqttTopicTemplate("A/B/{foo}/D");
|
||||||
|
template.WithParameter("foo", "e/f");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IgnoresEmptyParameters()
|
||||||
|
{
|
||||||
|
var template = new MqttTopicTemplate("A/B/{}/D");
|
||||||
|
Assert.IsFalse(template.Parameters.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void AcceptsValidTopics()
|
||||||
|
{
|
||||||
|
_ = new MqttTopicTemplate("A");
|
||||||
|
_ = new MqttTopicTemplate("/");
|
||||||
|
_ = new MqttTopicTemplate("/A");
|
||||||
|
_ = new MqttTopicTemplate("A/#");
|
||||||
|
_ = new MqttTopicTemplate("$SYS/#");
|
||||||
|
_ = new MqttTopicTemplate("////");
|
||||||
|
_ = new MqttTopicTemplate("A/B/{foo}/D/");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void DynamicRoutingSupport()
|
||||||
|
{
|
||||||
|
var v1template = new MqttTopicTemplate("A/v1/{id}/F");
|
||||||
|
var v2template = new MqttTopicTemplate("A/v2/foo/{id}/{bar}");
|
||||||
|
var incoming_v1_topic = "A/v1/32/F";
|
||||||
|
|
||||||
|
var compat_v2_topic = v2template
|
||||||
|
.WithParameterValuesFrom(
|
||||||
|
v1template.ParseParameterValues(incoming_v1_topic))
|
||||||
|
.WithParameter("bar", "unknown");
|
||||||
|
|
||||||
|
// id: 32 was relocated and bar: unknown added
|
||||||
|
Assert.AreEqual("A/v2/foo/32/unknown", compat_v2_topic.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SubscriptionSupport()
|
||||||
|
{
|
||||||
|
var template = new MqttTopicTemplate("A/v1/{param}/F");
|
||||||
|
var filter = template.BuildFilter()
|
||||||
|
.WithAtLeastOnceQoS()
|
||||||
|
.WithNoLocal()
|
||||||
|
.Build();
|
||||||
|
Assert.AreEqual("A/v1/+/F", filter.Topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilter()
|
||||||
|
{
|
||||||
|
var template1 = new MqttTopicTemplate("A/v1/{param}/F");
|
||||||
|
var template2 = new MqttTopicTemplate("A/v1/E/F");
|
||||||
|
var template3 = new MqttTopicTemplate("A/v1/E/F/G/H");
|
||||||
|
var canonicalFilter = MqttTopicTemplate.FindCanonicalPrefix(new[] { template1, template2, template3 });
|
||||||
|
// possible improvement: Assert.AreEqual("A/v1/+/F", canonicalFilter.TopicFilter);
|
||||||
|
Assert.AreEqual("A/v1/+", canonicalFilter.TopicFilter);
|
||||||
|
Assert.AreEqual("A/v1/+/#", canonicalFilter.TopicTreeRootFilter);
|
||||||
|
|
||||||
|
var template2b = new MqttTopicTemplate("A/v1/E/X");
|
||||||
|
canonicalFilter = MqttTopicTemplate.FindCanonicalPrefix(new[] { template1, template2, template3, template2b });
|
||||||
|
Assert.AreEqual("A/v1/+", canonicalFilter.Template);
|
||||||
|
Assert.AreEqual("A/v1/+", canonicalFilter.TopicFilter);
|
||||||
|
Assert.AreEqual("A/v1/+/#", canonicalFilter.TopicTreeRootFilter);
|
||||||
|
|
||||||
|
|
||||||
|
var template4 = new MqttTopicTemplate("A/v2/{prefix}");
|
||||||
|
var canonicalFilter2 = MqttTopicTemplate.FindCanonicalPrefix(new[] { template1, template2, template3, template4 });
|
||||||
|
Assert.AreEqual("A/+", canonicalFilter2.TopicFilter);
|
||||||
|
Assert.AreEqual("A/+/#", canonicalFilter2.TopicTreeRootFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilterSimple1()
|
||||||
|
{
|
||||||
|
var template = MqttTopicTemplate.FindCanonicalPrefix(new[]
|
||||||
|
{
|
||||||
|
new MqttTopicTemplate("A"),
|
||||||
|
new MqttTopicTemplate("B")
|
||||||
|
});
|
||||||
|
Assert.AreEqual("#", template.TopicFilter);
|
||||||
|
Assert.AreEqual("#", template.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilterSimple2()
|
||||||
|
{
|
||||||
|
var template = MqttTopicTemplate.FindCanonicalPrefix(new[]
|
||||||
|
{
|
||||||
|
new MqttTopicTemplate("A/v1/{param}/F/"),
|
||||||
|
new MqttTopicTemplate("A/v1/E/X")
|
||||||
|
});
|
||||||
|
Assert.AreEqual("A/v1/+", template.TopicFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilterSimple3()
|
||||||
|
{
|
||||||
|
var template = MqttTopicTemplate.FindCanonicalPrefix(new[]
|
||||||
|
{
|
||||||
|
new MqttTopicTemplate("A/v1/{param}/F"),
|
||||||
|
new MqttTopicTemplate("A/v1/{param}/F/X")
|
||||||
|
});
|
||||||
|
Assert.AreEqual("A/v1/+/+", template.TopicFilter);
|
||||||
|
Assert.AreEqual("A/v1/{param}/+", template.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilterSimple4()
|
||||||
|
{
|
||||||
|
var template = MqttTopicTemplate.FindCanonicalPrefix(new[]
|
||||||
|
{
|
||||||
|
new MqttTopicTemplate("A/v1/{param}/FFA"),
|
||||||
|
new MqttTopicTemplate("A/v1/{param}/FX")
|
||||||
|
});
|
||||||
|
Assert.AreEqual("A/v1/+/+", template.TopicFilter);
|
||||||
|
Assert.AreEqual("A/v1/{param}/+", template.Template);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilterSimple5()
|
||||||
|
{
|
||||||
|
var template = MqttTopicTemplate.FindCanonicalPrefix(new[]
|
||||||
|
{
|
||||||
|
new MqttTopicTemplate("A/v1/E/F/G/H"),
|
||||||
|
new MqttTopicTemplate("A/v2/{param}")
|
||||||
|
});
|
||||||
|
Assert.AreEqual("A/+", template.TopicFilter);
|
||||||
|
Assert.AreEqual("A/+/#", template.TopicTreeRootFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalPrefixFilterSimple6()
|
||||||
|
{
|
||||||
|
var template = MqttTopicTemplate.FindCanonicalPrefix(new[]
|
||||||
|
{
|
||||||
|
new MqttTopicTemplate("A/{version}/E/F/G/H"),
|
||||||
|
new MqttTopicTemplate("A/{version}/{param}")
|
||||||
|
});
|
||||||
|
Assert.AreEqual("A/+/+", template.TopicFilter);
|
||||||
|
Assert.AreEqual("A/+/+/#", template.TopicTreeRootFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -45,6 +46,34 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Execute_Success_Parameters_Propagated_Correctly()
|
||||||
|
{
|
||||||
|
var paramValue = "123";
|
||||||
|
var parameters = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" }
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
||||||
|
await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}");
|
||||||
|
|
||||||
|
responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
||||||
|
|
||||||
|
using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder()
|
||||||
|
.WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()))
|
||||||
|
{
|
||||||
|
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters);
|
||||||
|
|
||||||
|
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0()
|
public Task Execute_Success_With_QoS_0()
|
||||||
{
|
{
|
||||||
@@ -135,7 +164,7 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
|
|
||||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||||
{
|
{
|
||||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,5 +251,32 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||||
|
{
|
||||||
|
internal const string ExpectedParamName = "test_param_name";
|
||||||
|
|
||||||
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
|
{
|
||||||
|
if (context.Parameters == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Parameters dictionary expected to be not null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present");
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}";
|
||||||
|
var responseTopic = requestTopic + "/response";
|
||||||
|
|
||||||
|
return new MqttRpcTopicPair
|
||||||
|
{
|
||||||
|
RequestTopic = requestTopic,
|
||||||
|
ResponseTopic = responseTopic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
|
|
||||||
using (var client = factory.CreateMqttClient())
|
using (var client = factory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
var options = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("ws://a.b/mqtt")).WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||||
await client.ConnectAsync(options).ConfigureAwait(false);
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "A/B/C"
|
Topic = "A/B/C"
|
||||||
};
|
};
|
||||||
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
var publishPacket = new MqttPublishPacket
|
var publishPacket = new MqttPublishPacket
|
||||||
{
|
{
|
||||||
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
|
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
|
||||||
Payload = payload
|
PayloadSegment = new ArraySegment<byte>(payload)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var serializationHelper = new MqttPacketSerializationHelper();
|
var serializationHelper = new MqttPacketSerializationHelper();
|
||||||
|
|
||||||
var buffer = serializationHelper.Encode(publishPacket);
|
var buffer = serializationHelper.Encode(publishPacket);
|
||||||
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
|
|
||||||
Assert.IsNotNull(publishPacketCopy);
|
Assert.IsNotNull(publishPacketCopy);
|
||||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
|
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
|
||||||
|
|
||||||
// Now modify the payload and test again.
|
// Now modify the payload and test again.
|
||||||
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
|
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
|
||||||
|
|
||||||
buffer = serializationHelper.Encode(publishPacket);
|
buffer = serializationHelper.Encode(publishPacket);
|
||||||
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
|
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
|
||||||
|
|
||||||
Assert.IsNotNull(publishPacketCopy2);
|
Assert.IsNotNull(publishPacketCopy2);
|
||||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
|
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "A/B/C"
|
Topic = "A/B/C"
|
||||||
};
|
};
|
||||||
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
using (var adapter = new MqttChannelAdapter(
|
using (var adapter = new MqttChannelAdapter(
|
||||||
channel,
|
channel,
|
||||||
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
|
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
|
||||||
null,
|
|
||||||
new MqttNetEventLogger()))
|
new MqttNetEventLogger()))
|
||||||
{
|
{
|
||||||
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
||||||
{
|
{
|
||||||
var channel = new MemoryMqttChannel(buffer);
|
var channel = new MemoryMqttChannel(buffer);
|
||||||
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
|
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
|
||||||
|
|
||||||
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
return packetFormatterAdapter.ProtocolVersion;
|
return packetFormatterAdapter.ProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttBufferReader ReaderFactory(byte[] data)
|
|
||||||
{
|
|
||||||
var reader = new MqttBufferReader();
|
|
||||||
reader.SetBuffer(data, 0, data.Length);
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
|
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
|
||||||
{
|
{
|
||||||
var writer = bufferWriter ?? WriterFactory();
|
var writer = bufferWriter ?? WriterFactory();
|
||||||
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
|
|
||||||
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
|
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
|
||||||
{
|
{
|
||||||
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||||
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "Topic",
|
Topic = "Topic",
|
||||||
ResponseTopic = "/Response",
|
ResponseTopic = "/Response",
|
||||||
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||||
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
|
||||||
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
||||||
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
|
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "Topic",
|
Topic = "Topic",
|
||||||
ResponseTopic = "/Response",
|
ResponseTopic = "/Response",
|
||||||
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||||
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
|
||||||
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
||||||
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
|
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Formatter
|
namespace MQTTnet.Tests.Formatter
|
||||||
@@ -48,22 +49,31 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
|
Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||||
|
public void Throw_If_String_Too_Long()
|
||||||
|
{
|
||||||
|
var writer = new MqttBufferWriter(4096, 65535);
|
||||||
|
|
||||||
|
writer.WriteString(string.Empty.PadLeft(65536));
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Write_And_Read_Multiple_Times()
|
public void Write_And_Read_Multiple_Times()
|
||||||
{
|
{
|
||||||
var writer = new MqttBufferWriter(4096, 65535);
|
var writer = new MqttBufferWriter(4096, 65535);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteBinaryData(new byte[1234]);
|
writer.WriteBinary(new byte[1234]);
|
||||||
writer.WriteByte(0x01);
|
writer.WriteByte(0x01);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574489);
|
writer.WriteVariableByteInteger(8574489);
|
||||||
writer.WriteBinaryData(new byte[48]);
|
writer.WriteBinary(new byte[48]);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(new byte[3]);
|
writer.WriteBinary(new byte[3]);
|
||||||
|
|
||||||
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -17,7 +18,7 @@ namespace MQTTnet.Tests.Internal
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Connect_Send_Receive()
|
public async Task Connect_Send_Receive()
|
||||||
{
|
{
|
||||||
var crossPlatformSocket = new CrossPlatformSocket();
|
var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp);
|
||||||
await crossPlatformSocket.ConnectAsync("www.google.de", 80, CancellationToken.None);
|
await crossPlatformSocket.ConnectAsync("www.google.de", 80, CancellationToken.None);
|
||||||
|
|
||||||
var requestBuffer = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\nHost: www.google.de\r\n\r\n");
|
var requestBuffer = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\nHost: www.google.de\r\n\r\n");
|
||||||
@@ -36,7 +37,7 @@ namespace MQTTnet.Tests.Internal
|
|||||||
[ExpectedException(typeof(OperationCanceledException))]
|
[ExpectedException(typeof(OperationCanceledException))]
|
||||||
public async Task Try_Connect_Invalid_Host()
|
public async Task Try_Connect_Invalid_Host()
|
||||||
{
|
{
|
||||||
var crossPlatformSocket = new CrossPlatformSocket();
|
var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp);
|
||||||
|
|
||||||
var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||||
cancellationToken.Token.Register(() => crossPlatformSocket.Dispose());
|
cancellationToken.Token.Register(() => crossPlatformSocket.Dispose());
|
||||||
@@ -65,7 +66,7 @@ namespace MQTTnet.Tests.Internal
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Set_Options()
|
public void Set_Options()
|
||||||
{
|
{
|
||||||
var crossPlatformSocket = new CrossPlatformSocket();
|
var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp);
|
||||||
|
|
||||||
Assert.IsFalse(crossPlatformSocket.ReuseAddress);
|
Assert.IsFalse(crossPlatformSocket.ReuseAddress);
|
||||||
crossPlatformSocket.ReuseAddress = true;
|
crossPlatformSocket.ReuseAddress = true;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netcoreapp3.1;net7.0</TargetFrameworks>
|
||||||
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net48</TargetFrameworks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<LangVersion>7.3</LangVersion>
|
<LangVersion>7.3</LangVersion>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||||
|
<ProjectReference Include="..\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj" />
|
||||||
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
|
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
|
||||||
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
|
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
|
||||||
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
|
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
|
||||||
CollectionAssert.AreEqual(applicationMessage.Payload, receivedMessage.Payload);
|
CollectionAssert.AreEqual(applicationMessage.PayloadSegment.ToArray(), receivedMessage.PayloadSegment.ToArray());
|
||||||
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
|
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.MQTTv5
|
namespace MQTTnet.Tests.MQTTv5
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public void AssertReceivedCountEquals(int expectedCount)
|
public void AssertReceivedCountEquals(int expectedCount)
|
||||||
{
|
{
|
||||||
lock (_receivedEventArgs)
|
Assert.AreEqual(expectedCount, Count);
|
||||||
{
|
|
||||||
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GeneratePayloadSequence()
|
public string GeneratePayloadSequence()
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
{
|
{
|
||||||
readonly List<string> _clientErrors = new List<string>();
|
readonly List<string> _clientErrors = new List<string>();
|
||||||
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
||||||
|
|
||||||
readonly List<Exception> _exceptions = new List<Exception>();
|
readonly List<Exception> _exceptions = new List<Exception>();
|
||||||
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
||||||
readonly MqttProtocolVersion _protocolVersion;
|
readonly MqttProtocolVersion _protocolVersion;
|
||||||
@@ -40,6 +39,8 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
_protocolVersion = protocolVersion;
|
_protocolVersion = protocolVersion;
|
||||||
TestContext = testContext;
|
TestContext = testContext;
|
||||||
|
|
||||||
|
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
|
||||||
|
|
||||||
ServerLogger.LogMessagePublished += (s, e) =>
|
ServerLogger.LogMessagePublished += (s, e) =>
|
||||||
{
|
{
|
||||||
if (Debugger.IsAttached)
|
if (Debugger.IsAttached)
|
||||||
@@ -64,12 +65,15 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||||
|
{
|
||||||
|
if (!IgnoreClientLogErrors)
|
||||||
{
|
{
|
||||||
lock (_clientErrors)
|
lock (_clientErrors)
|
||||||
{
|
{
|
||||||
_clientErrors.Add(e.LogMessage.ToString());
|
_clientErrors.Add(e.LogMessage.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,12 +216,9 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public IMqttClient CreateClient()
|
public IMqttClient CreateClient()
|
||||||
{
|
{
|
||||||
lock (_clients)
|
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
|
||||||
{
|
|
||||||
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
|
|
||||||
|
|
||||||
var client = Factory.CreateMqttClient(logger);
|
var client = Factory.CreateMqttClient(logger);
|
||||||
_clients.Add(client);
|
|
||||||
|
|
||||||
client.ConnectingAsync += e =>
|
client.ConnectingAsync += e =>
|
||||||
{
|
{
|
||||||
@@ -234,8 +235,12 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
return client;
|
lock (_clients)
|
||||||
|
{
|
||||||
|
_clients.Add(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptions CreateDefaultClientOptions()
|
public MqttClientOptions CreateDefaultClientOptions()
|
||||||
@@ -245,19 +250,23 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
||||||
{
|
{
|
||||||
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
|
return Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithProtocolVersion(_protocolVersion)
|
||||||
|
.WithTcpServer("127.0.0.1", ServerPort)
|
||||||
|
.WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILowLevelMqttClient CreateLowLevelClient()
|
public ILowLevelMqttClient CreateLowLevelClient()
|
||||||
{
|
|
||||||
lock (_clients)
|
|
||||||
{
|
{
|
||||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||||
|
|
||||||
|
lock (_lowLevelClients)
|
||||||
|
{
|
||||||
_lowLevelClients.Add(client);
|
_lowLevelClients.Add(client);
|
||||||
|
}
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public MqttServer CreateServer(MqttServerOptions options)
|
public MqttServer CreateServer(MqttServerOptions options)
|
||||||
{
|
{
|
||||||
@@ -289,6 +298,10 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_clients)
|
||||||
{
|
{
|
||||||
foreach (var mqttClient in _clients)
|
foreach (var mqttClient in _clients)
|
||||||
{
|
{
|
||||||
@@ -306,11 +319,19 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clients.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lowLevelClients)
|
||||||
|
{
|
||||||
foreach (var lowLevelMqttClient in _lowLevelClients)
|
foreach (var lowLevelMqttClient in _lowLevelClients)
|
||||||
{
|
{
|
||||||
lowLevelMqttClient.Dispose();
|
lowLevelMqttClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lowLevelClients.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Server?.StopAsync().GetAwaiter().GetResult();
|
Server?.StopAsync().GetAwaiter().GetResult();
|
||||||
@@ -324,13 +345,24 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
Server?.Dispose();
|
Server?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Server = null;
|
||||||
|
|
||||||
ThrowIfLogErrors();
|
ThrowIfLogErrors();
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForFullGCComplete();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
if (_exceptions.Any())
|
if (_exceptions.Any())
|
||||||
{
|
{
|
||||||
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
|
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task<MqttServer> StartServer()
|
public Task<MqttServer> StartServer()
|
||||||
{
|
{
|
||||||
@@ -345,7 +377,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
var options = optionsBuilder.Build();
|
var options = optionsBuilder.Build();
|
||||||
var server = CreateServer(options);
|
var server = CreateServer(options);
|
||||||
await server.StartAsync();
|
await server.StartAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// The OS has chosen the port to we have to properly expose it to the tests.
|
// The OS has chosen the port to we have to properly expose it to the tests.
|
||||||
ServerPort = options.DefaultEndpointOptions.Port;
|
ServerPort = options.DefaultEndpointOptions.Port;
|
||||||
@@ -372,20 +404,25 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ThrowIfLogErrors()
|
public void ThrowIfLogErrors()
|
||||||
|
{
|
||||||
|
if (!IgnoreServerLogErrors)
|
||||||
{
|
{
|
||||||
lock (_serverErrors)
|
lock (_serverErrors)
|
||||||
{
|
{
|
||||||
if (!IgnoreServerLogErrors && _serverErrors.Count > 0)
|
if (_serverErrors.Count > 0)
|
||||||
{
|
{
|
||||||
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
|
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
|
||||||
Console.WriteLine(message);
|
Console.WriteLine(message);
|
||||||
throw new Exception(message);
|
throw new Exception(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IgnoreClientLogErrors)
|
||||||
|
{
|
||||||
lock (_clientErrors)
|
lock (_clientErrors)
|
||||||
{
|
{
|
||||||
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
|
if (_clientErrors.Count > 0)
|
||||||
{
|
{
|
||||||
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
||||||
Console.WriteLine(message);
|
Console.WriteLine(message);
|
||||||
@@ -393,13 +430,24 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void TrackException(Exception exception)
|
public void TrackException(Exception exception)
|
||||||
{
|
{
|
||||||
|
if (exception == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_exceptions)
|
lock (_exceptions)
|
||||||
{
|
{
|
||||||
_exceptions.Add(exception);
|
_exceptions.Add(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
TrackException(e.Exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -11,7 +12,7 @@ using MQTTnet.Protocol;
|
|||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class MqttApplicationMessageBuilder_Tests
|
public sealed class MqttApplicationMessageBuilder_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateApplicationMessage_TopicOnly()
|
public void CreateApplicationMessage_TopicOnly()
|
||||||
@@ -29,7 +30,7 @@ namespace MQTTnet.Tests
|
|||||||
Assert.AreEqual("xyz", message.Topic);
|
Assert.AreEqual("xyz", message.Topic);
|
||||||
Assert.IsFalse(message.Retain);
|
Assert.IsFalse(message.Retain);
|
||||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
|
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -41,7 +42,7 @@ namespace MQTTnet.Tests
|
|||||||
Assert.AreEqual("123", message.Topic);
|
Assert.AreEqual("123", message.Topic);
|
||||||
Assert.IsFalse(message.Retain);
|
Assert.IsFalse(message.Retain);
|
||||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
|
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttApplicationMessageTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void PayloadSegment()
|
||||||
|
{
|
||||||
|
var message = new MqttApplicationMessage();
|
||||||
|
Assert.AreEqual(0, message.Payload.Length);
|
||||||
|
Assert.AreEqual(0, message.PayloadSegment.Count);
|
||||||
|
|
||||||
|
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||||
|
|
||||||
|
message.Payload = new byte[] { 1, 2 };
|
||||||
|
Assert.AreEqual(2, message.Payload.Length);
|
||||||
|
Assert.AreEqual(2, message.PayloadSegment.Count);
|
||||||
|
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||||
|
|
||||||
|
message.Payload = new byte[] { 1, 2, 3 };
|
||||||
|
Assert.AreEqual(3, message.Payload.Length);
|
||||||
|
Assert.AreEqual(3, message.PayloadSegment.Count);
|
||||||
|
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||||
|
|
||||||
|
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
|
||||||
|
Assert.AreEqual(3, message.Payload.Length);
|
||||||
|
Assert.AreEqual(3, message.PayloadSegment.Count);
|
||||||
|
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||||
|
|
||||||
|
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
|
||||||
|
Assert.AreEqual(1, message.Payload.Length);
|
||||||
|
Assert.AreEqual(1, message.PayloadSegment.Count);
|
||||||
|
Assert.IsFalse(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
|
|||||||
{
|
{
|
||||||
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
|
var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
|
||||||
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
|
|||||||
writer.WriteString("1234567890");
|
writer.WriteString("1234567890");
|
||||||
Assert.AreEqual(10 + 2, writer.Length);
|
Assert.AreEqual(10 + 2, writer.Length);
|
||||||
|
|
||||||
writer.WriteBinaryData(new byte[300]);
|
writer.WriteBinary(new byte[300]);
|
||||||
Assert.AreEqual(300 + 2 + 12, writer.Length);
|
Assert.AreEqual(300 + 2 + 12, writer.Length);
|
||||||
|
|
||||||
writer.WriteBinaryData(new byte[5000]);
|
writer.WriteBinary(new byte[5000]);
|
||||||
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
|
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace MQTTnet.Tests
|
|||||||
public async Task Dispose_Channel_While_Used()
|
public async Task Dispose_Channel_While_Used()
|
||||||
{
|
{
|
||||||
var ct = new CancellationTokenSource();
|
var ct = new CancellationTokenSource();
|
||||||
var serverSocket = new CrossPlatformSocket(AddressFamily.InterNetwork);
|
var serverSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -38,7 +38,7 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}, ct.Token);
|
}, ct.Token);
|
||||||
|
|
||||||
var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork);
|
var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
||||||
await clientSocket.ConnectAsync("localhost", 50001, CancellationToken.None);
|
await clientSocket.ConnectAsync("localhost", 50001, CancellationToken.None);
|
||||||
|
|
||||||
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null);
|
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1)));
|
await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1)));
|
||||||
|
|
||||||
var client = new CrossPlatformSocket(AddressFamily.InterNetwork);
|
var client = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
||||||
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
|
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
|
||||||
|
|
||||||
// Don't send anything. The server should close the connection.
|
// Don't send anything. The server should close the connection.
|
||||||
@@ -54,7 +54,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
// Send an invalid packet and ensure that the server will close the connection and stay in a waiting state
|
// Send an invalid packet and ensure that the server will close the connection and stay in a waiting state
|
||||||
// forever. This is security related.
|
// forever. This is security related.
|
||||||
var client = new CrossPlatformSocket(AddressFamily.InterNetwork);
|
var client = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
||||||
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
|
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
|
||||||
|
|
||||||
var buffer = Encoding.UTF8.GetBytes("Garbage");
|
var buffer = Encoding.UTF8.GetBytes("Garbage");
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var server = await testEnvironment.StartServer();
|
var server = await testEnvironment.StartServer();
|
||||||
server.InterceptingPublishAsync += e =>
|
server.InterceptingPublishAsync += e =>
|
||||||
{
|
{
|
||||||
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
|
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("extended"));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var isIntercepted = false;
|
var isIntercepted = false;
|
||||||
c2.ApplicationMessageReceivedAsync += e =>
|
c2.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
|
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()), StringComparison.Ordinal) == 0;
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -425,7 +425,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "/test/1",
|
Topic = "/test/1",
|
||||||
Payload = Encoding.UTF8.GetBytes("true"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("true")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@@ -598,8 +598,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
// c
|
// c
|
||||||
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
var flow = string.Join(string.Empty, events);
|
var flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("c", flow);
|
Assert.AreEqual("c", flow);
|
||||||
|
|
||||||
@@ -607,10 +606,8 @@ namespace MQTTnet.Tests.Server
|
|||||||
// Connect client with same client ID. Should disconnect existing client.
|
// Connect client with same client ID. Should disconnect existing client.
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
|
|
||||||
Assert.AreEqual("cdc", flow);
|
Assert.AreEqual("cdc", flow);
|
||||||
|
|
||||||
c2.ApplicationMessageReceivedAsync += e =>
|
c2.ApplicationMessageReceivedAsync += e =>
|
||||||
@@ -628,8 +625,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
// r
|
// r
|
||||||
await c2.PublishStringAsync("topic");
|
await c2.PublishStringAsync("topic");
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("cdcr", flow);
|
Assert.AreEqual("cdcr", flow);
|
||||||
|
|
||||||
@@ -639,16 +635,17 @@ namespace MQTTnet.Tests.Server
|
|||||||
await c1.TryDisconnectAsync();
|
await c1.TryDisconnectAsync();
|
||||||
Assert.AreEqual(false, c1.IsConnected);
|
Assert.AreEqual(false, c1.IsConnected);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
// d
|
// d
|
||||||
Assert.AreEqual(true, c2.IsConnected);
|
Assert.AreEqual(true, c2.IsConnected);
|
||||||
await c2.DisconnectAsync();
|
await c2.DisconnectAsync();
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("cdcrd", flow);
|
Assert.AreEqual("cdcrd", flow);
|
||||||
}
|
}
|
||||||
@@ -783,7 +780,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var client1 = await testEnvironment.ConnectClient();
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
client1.ApplicationMessageReceivedAsync += e =>
|
client1.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
receivedBody = e.ApplicationMessage.Payload;
|
receivedBody = e.ApplicationMessage.PayloadSegment.ToArray();
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -852,11 +849,11 @@ namespace MQTTnet.Tests.Server
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
await Task.Delay(100);
|
// Stopping the server should disconnect the connection with the client
|
||||||
|
// which, it turn, will fire the disconnected event at the client.
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
await Task.Delay(100);
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, disconnectCalled);
|
Assert.AreEqual(1, disconnectCalled);
|
||||||
}
|
}
|
||||||
@@ -954,7 +951,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
client.InspectPackage += e =>
|
client.InspectPacketAsync += e =>
|
||||||
{
|
{
|
||||||
if (e.Buffer.Length > 0)
|
if (e.Buffer.Length > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,426 @@
|
|||||||
|
#if !(NET452 || NET461 || NET48)
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
// missing certificate builder api means tests won't work for older frameworks
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class HotSwapCerts_Tests
|
||||||
|
{
|
||||||
|
static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ClientCertChangeWithoutServerUpdateFailsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.HotSwapClientCert();
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.WaitForConnectToFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ClientCertChangeWithServerUpdateAcceptsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.HotSwapClientCert();
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ServerCertChangeWithClientCertUpdateAllowsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
server.HotSwapServerCert();
|
||||||
|
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ServerCertChangeWithoutClientCertUpdateFailsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
server.HotSwapServerCert();
|
||||||
|
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.WaitForConnectToFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||||
|
{
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ClientTestHarness : IDisposable
|
||||||
|
{
|
||||||
|
readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider();
|
||||||
|
|
||||||
|
IMqttClient _client;
|
||||||
|
|
||||||
|
public string ClientId => _client.Options.ClientId;
|
||||||
|
|
||||||
|
public Task Connect()
|
||||||
|
{
|
||||||
|
return Run_Client_Connection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
_hotSwapClient.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 GetCurrentClientCert()
|
||||||
|
{
|
||||||
|
var result = _hotSwapClient.GetCertificates()[0];
|
||||||
|
return new X509Certificate2(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapClientCert()
|
||||||
|
{
|
||||||
|
_hotSwapClient.HotSwapCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewServerCert(X509Certificate2 serverCert)
|
||||||
|
{
|
||||||
|
_hotSwapClient.InstallNewServerCert(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForConnectOrFail(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
|
||||||
|
if (!_client.IsConnected)
|
||||||
|
{
|
||||||
|
_client.ReconnectAsync().Wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForConnect(timeout);
|
||||||
|
|
||||||
|
Assert.IsNotNull(_client, "Client was never initialized");
|
||||||
|
Assert.IsTrue(_client.IsConnected, $"Client connection failed after {timeout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForConnectToFail(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
Assert.IsFalse(_client.IsConnected, "Client should be disconnected before waiting for connect.");
|
||||||
|
|
||||||
|
WaitForConnect(timeout);
|
||||||
|
|
||||||
|
Assert.IsNotNull(_client, "Client was never initialized");
|
||||||
|
Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForDisconnect(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForDisconnectOrFail(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
WaitForDisconnect(timeout);
|
||||||
|
|
||||||
|
Assert.IsNotNull(_client, "Client was never initialized");
|
||||||
|
Assert.IsFalse(_client.IsConnected, $"Client connection should have disconnected after {timeout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Run_Client_Connection()
|
||||||
|
{
|
||||||
|
var optionsBuilder = new MqttClientOptionsBuilder()
|
||||||
|
.WithTlsOptions(
|
||||||
|
o => o.WithClientCertificatesProvider(_hotSwapClient)
|
||||||
|
.WithCertificateValidationHandler(_hotSwapClient.OnCertificateValidation)
|
||||||
|
.WithSslProtocols(SslProtocols.Tls12))
|
||||||
|
.WithTcpServer("localhost")
|
||||||
|
.WithCleanSession()
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V500);
|
||||||
|
|
||||||
|
var mqttClientOptions = optionsBuilder.Build();
|
||||||
|
|
||||||
|
var factory = new MqttFactory();
|
||||||
|
var mqttClient = factory.CreateMqttClient();
|
||||||
|
_client = mqttClient;
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForConnect(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ServerTestHarness : IDisposable
|
||||||
|
{
|
||||||
|
readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider();
|
||||||
|
|
||||||
|
MqttServer _server;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_server != null)
|
||||||
|
{
|
||||||
|
_server.StopAsync().Wait();
|
||||||
|
_server.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_hotSwapServer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ForceDisconnectAsync(ClientTestHarness client)
|
||||||
|
{
|
||||||
|
return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 GetCurrentServerCert()
|
||||||
|
{
|
||||||
|
return _hotSwapServer.GetCertificate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapServerCert()
|
||||||
|
{
|
||||||
|
_hotSwapServer.HotSwapCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewClientCert(X509Certificate2 serverCert)
|
||||||
|
{
|
||||||
|
_hotSwapServer.InstallNewClientCert(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartServer()
|
||||||
|
{
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(_hotSwapServer)
|
||||||
|
.WithRemoteCertificateValidationCallback(_hotSwapServer.RemoteCertificateValidationCallback)
|
||||||
|
.WithEncryptedEndpoint()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true;
|
||||||
|
|
||||||
|
_server = mqttFactory.CreateMqttServer(mqttServerOptions);
|
||||||
|
return _server.StartAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable
|
||||||
|
{
|
||||||
|
X509Certificate2Collection _certificates;
|
||||||
|
ConcurrentBag<X509Certificate2> _serverCerts = new ConcurrentBag<X509Certificate2>();
|
||||||
|
|
||||||
|
public HotSwappableClientCertProvider()
|
||||||
|
{
|
||||||
|
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_certificates != null)
|
||||||
|
{
|
||||||
|
foreach (var certs in _certificates)
|
||||||
|
{
|
||||||
|
#if !NET452
|
||||||
|
certs.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509CertificateCollection GetCertificates()
|
||||||
|
{
|
||||||
|
return new X509Certificate2Collection(_certificates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapCert()
|
||||||
|
{
|
||||||
|
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewServerCert(X509Certificate2 serverCert)
|
||||||
|
{
|
||||||
|
_serverCerts.Add(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext)
|
||||||
|
{
|
||||||
|
var serverCerts = _serverCerts.ToArray();
|
||||||
|
|
||||||
|
var providedCert = certContext.Certificate.GetRawCertData();
|
||||||
|
for (int i = 0, n = serverCerts.Length; i < n; i++)
|
||||||
|
{
|
||||||
|
var currentcert = serverCerts[i];
|
||||||
|
|
||||||
|
if (currentcert.RawData.SequenceEqual(providedCert))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable
|
||||||
|
{
|
||||||
|
readonly ConcurrentBag<X509Certificate2> _clientCerts = new ConcurrentBag<X509Certificate2>();
|
||||||
|
X509Certificate2 _certificate;
|
||||||
|
|
||||||
|
public HotSwappableServerCertProvider()
|
||||||
|
{
|
||||||
|
_certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
#if !NET452
|
||||||
|
_certificate.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 GetCertificate()
|
||||||
|
{
|
||||||
|
return _certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapCert()
|
||||||
|
{
|
||||||
|
var newCert = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||||
|
var oldCert = Interlocked.Exchange(ref _certificate, newCert);
|
||||||
|
#if !NET452
|
||||||
|
oldCert.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewClientCert(X509Certificate2 certificate)
|
||||||
|
{
|
||||||
|
_clientCerts.Add(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
|
{
|
||||||
|
var serverCerts = _clientCerts.ToArray();
|
||||||
|
|
||||||
|
var providedCert = certificate.GetRawCertData();
|
||||||
|
for (int i = 0, n = serverCerts.Length; i < n; i++)
|
||||||
|
{
|
||||||
|
var currentCert = serverCerts[i];
|
||||||
|
|
||||||
|
if (currentCert.RawData.SequenceEqual(providedCert))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttRetainedMessageManager_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttRetainedMessageManager_GetUndefinedTopic()
|
||||||
|
{
|
||||||
|
var logger = new Mockups.TestLogger();
|
||||||
|
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
|
||||||
|
var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger);
|
||||||
|
var task = retainedMessagesManager.GetMessage("undefined");
|
||||||
|
Assert.IsNotNull(task, "Task should not be null");
|
||||||
|
var result = await task;
|
||||||
|
Assert.IsNull(result, "Null result expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -221,14 +221,17 @@ namespace MQTTnet.Tests.Server
|
|||||||
var eventContainer = new MqttServerEventContainer();
|
var eventContainer = new MqttServerEventContainer();
|
||||||
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
||||||
|
|
||||||
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
var session = new MqttSession(new MqttConnectPacket
|
||||||
|
{
|
||||||
|
ClientId = ""
|
||||||
|
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
||||||
|
|
||||||
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
||||||
{
|
{
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _);
|
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||||
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public sealed class Publish_Tests : BaseTestClass
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Return_Success_When_Subscribed()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var sender = await testEnvironment.ConnectClient();
|
|
||||||
var receiver = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
await receiver.SubscribeAsync("A");
|
|
||||||
|
|
||||||
// AtLeastOnce is required to get an ACK packet from the server.
|
|
||||||
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
|
||||||
|
|
||||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var sender = await testEnvironment.ConnectClient();
|
|
||||||
var receiver = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
await receiver.SubscribeAsync("A");
|
|
||||||
|
|
||||||
// AtLeastOnce is required to get an ACK packet from the server.
|
|
||||||
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
|
||||||
|
|
||||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,10 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
@@ -27,5 +30,118 @@ namespace MQTTnet.Tests.Server
|
|||||||
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
|
||||||
|
// AtLeastOnce is required to get an ACK packet from the server.
|
||||||
|
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
||||||
|
|
||||||
|
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_Success_When_Subscribed()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
|
||||||
|
// AtLeastOnce is required to get an ACK packet from the server.
|
||||||
|
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
||||||
|
|
||||||
|
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Client_Enqueue()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||||
|
|
||||||
|
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
server.InterceptingClientEnqueueAsync += e =>
|
||||||
|
{
|
||||||
|
e.AcceptEnqueue = false;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Do not increase because the internal enqueue to the target client is not accepted!
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One"));
|
||||||
|
await receiverOne.SubscribeAsync("A");
|
||||||
|
var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne);
|
||||||
|
|
||||||
|
var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two"));
|
||||||
|
await receiverTwo.SubscribeAsync("A");
|
||||||
|
var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo);
|
||||||
|
|
||||||
|
var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three"));
|
||||||
|
await receiverThree.SubscribeAsync("A");
|
||||||
|
var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree);
|
||||||
|
|
||||||
|
server.InterceptingClientEnqueueAsync += e =>
|
||||||
|
{
|
||||||
|
if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessagesOne.AssertReceivedCountEquals(1);
|
||||||
|
receivedMessagesTwo.AssertReceivedCountEquals(0);
|
||||||
|
receivedMessagesThree.AssertReceivedCountEquals(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
|
||||||
|
|
||||||
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
|
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
|
||||||
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,28 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Do_Not_Use_Expired_Session()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3));
|
||||||
|
|
||||||
|
// Kill the client connection and ensure that the session will stay there but is expired after 3 seconds!
|
||||||
|
c1.Dispose();
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(6));
|
||||||
|
|
||||||
|
c1 = testEnvironment.CreateClient();
|
||||||
|
var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build();
|
||||||
|
|
||||||
|
var connectResult = await c1.ConnectAsync(options);
|
||||||
|
Assert.AreEqual(false, connectResult.IsSessionPresent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Fire_Deleted_Event()
|
public async Task Fire_Deleted_Event()
|
||||||
{
|
{
|
||||||
@@ -134,17 +156,13 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
|
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
|
||||||
|
|
||||||
var hasReceive = false;
|
var hasReceive = false;
|
||||||
|
|
||||||
void OnReceive()
|
void OnReceive()
|
||||||
{
|
|
||||||
if (!hasReceive)
|
|
||||||
{
|
{
|
||||||
hasReceive = true;
|
hasReceive = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Try to connect 50 clients at the same time.
|
// Try to connect 50 clients at the same time.
|
||||||
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
||||||
@@ -265,7 +283,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
server.InterceptingPublishAsync += e =>
|
server.InterceptingPublishAsync += e =>
|
||||||
{
|
{
|
||||||
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
|
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +374,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
{
|
{
|
||||||
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
|
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,29 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Subscribe_With_User_Properties()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
InterceptingSubscriptionEventArgs eventArgs = null;
|
||||||
|
server.InterceptingSubscriptionAsync += e =>
|
||||||
|
{
|
||||||
|
eventArgs = e;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
|
||||||
|
await client.SubscribeAsync(subscribeOptions);
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||||
public async Task Disconnect_While_Subscribing()
|
public async Task Disconnect_While_Subscribing()
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var topics = t.Value;
|
var topics = t.Value;
|
||||||
foreach (var topic in topics)
|
foreach (var topic in topics)
|
||||||
{
|
{
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||||
|
|
||||||
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
||||||
++currentValue;
|
++currentValue;
|
||||||
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
// UInt64 is limited to 8 levels
|
// UInt64 is limited to 8 levels
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var topic = sb.ToString();
|
var topic = sb.ToString();
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var topic = sb.ToString();
|
var topic = sb.ToString();
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l3 = "#";
|
var l3 = "#";
|
||||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l3 = "prop1";
|
var l3 = "prop1";
|
||||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
||||||
|
|
||||||
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l3 = "prop1";
|
var l3 = "prop1";
|
||||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
||||||
{
|
{
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Topic: " + topic);
|
Console.WriteLine("Topic: " + topic);
|
||||||
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
var topicToFind = topicsToFind[countDown];
|
var topicToFind = topicsToFind[countDown];
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _);
|
MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
|
||||||
|
|
||||||
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
|
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
|
||||||
if (result.IsSubscribed)
|
if (result.IsSubscribed)
|
||||||
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var eventContainer = new MqttServerEventContainer();
|
var eventContainer = new MqttServerEventContainer();
|
||||||
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
||||||
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
||||||
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
_clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
||||||
|
|
||||||
List<string> topicsToSubscribe;
|
List<string> topicsToSubscribe;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Tls_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
static X509Certificate2 CreateCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||||
|
{
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Tls_Swap_Test()
|
||||||
|
{
|
||||||
|
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||||
|
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
|
||||||
|
|
||||||
|
var firstOid = "1.3.6.1.5.5.7.3.1";
|
||||||
|
var secondOid = "1.3.6.1.5.5.7.3.2";
|
||||||
|
|
||||||
|
var certificateProvider = new CertificateProvider
|
||||||
|
{
|
||||||
|
CurrentCertificate = CreateCertificate(firstOid)
|
||||||
|
};
|
||||||
|
|
||||||
|
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
|
||||||
|
|
||||||
|
var serverOptions = serverOptionsBuilder.Build();
|
||||||
|
|
||||||
|
var server = testEnvironment.CreateServer(serverOptions);
|
||||||
|
|
||||||
|
var publishedCount = 0;
|
||||||
|
server.InterceptingPublishAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref publishedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await server.StartAsync();
|
||||||
|
|
||||||
|
var firstClient = await ConnectClientAsync(
|
||||||
|
testEnvironment,
|
||||||
|
args =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var firstClientReceivedCount = 0;
|
||||||
|
firstClient.ApplicationMessageReceivedAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref firstClientReceivedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await firstClient.SubscribeAsync("TestTopic1");
|
||||||
|
|
||||||
|
await firstClient.PublishAsync(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
|
}));
|
||||||
|
|
||||||
|
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
||||||
|
|
||||||
|
// Validate that the certificate was switched
|
||||||
|
var secondClient = await ConnectClientAsync(
|
||||||
|
testEnvironment,
|
||||||
|
args =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var secondClientReceivedCount = 0;
|
||||||
|
secondClient.ApplicationMessageReceivedAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref secondClientReceivedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await secondClient.SubscribeAsync("TestTopic2");
|
||||||
|
|
||||||
|
await firstClient.PublishAsync(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic2",
|
||||||
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic2",
|
||||||
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Ensure first client still works
|
||||||
|
await firstClient.PublishAsync(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(6, publishedCount);
|
||||||
|
Assert.AreEqual(4, firstClientReceivedCount);
|
||||||
|
Assert.AreEqual(2, secondClientReceivedCount);
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
|
||||||
|
{
|
||||||
|
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
||||||
|
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
|
||||||
|
.WithTcpServer("localhost", 8883)
|
||||||
|
.WithTlsOptions(
|
||||||
|
o =>
|
||||||
|
{
|
||||||
|
o.WithSslProtocols(SslProtocols.Tls12).WithCertificateValidationHandler(certValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
var clientOptions = clientOptionsBuilder.Build();
|
||||||
|
return await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class CertificateProvider : ICertificateProvider
|
||||||
|
{
|
||||||
|
public X509Certificate2 CurrentCertificate { get; set; }
|
||||||
|
|
||||||
|
public X509Certificate2 GetCertificate()
|
||||||
|
{
|
||||||
|
return CurrentCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -2,10 +2,14 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
|
|||||||
await client.UnsubscribeAsync("#");
|
await client.UnsubscribeAsync("#");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Unsubscribe_With_User_Properties()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
InterceptingUnsubscriptionEventArgs eventArgs = null;
|
||||||
|
server.InterceptingUnsubscriptionAsync += e =>
|
||||||
|
{
|
||||||
|
eventArgs = e;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var unsubscribeOptions = testEnvironment.Factory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
|
||||||
|
await client.UnsubscribeAsync(unsubscribeOptions);
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,18 +150,8 @@ namespace MQTTnet.Tests
|
|||||||
{
|
{
|
||||||
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
|
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
|
||||||
|
|
||||||
ulong topicHash;
|
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||||
ulong topicHashMask;
|
MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
|
|
||||||
ulong filterTopicHash;
|
|
||||||
ulong filterTopicHashMask;
|
|
||||||
bool filterTopicHasWildcard;
|
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(filter, out filterTopicHash, out filterTopicHashMask, out filterTopicHasWildcard);
|
|
||||||
|
|
||||||
if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
|
if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
long BytesReceived { get; }
|
long BytesReceived { get; }
|
||||||
|
|
||||||
bool IsReadingPacket { get; }
|
|
||||||
|
|
||||||
Task ConnectAsync(CancellationToken cancellationToken);
|
Task ConnectAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task DisconnectAsync(CancellationToken cancellationToken);
|
Task DisconnectAsync(CancellationToken cancellationToken);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace MQTTnet.Adapter
|
|||||||
Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
|
||||||
Task StartAsync(MqttServerOptions options, IMqttNetLogger logger);
|
Task StartAsync(MqttServerOptions options, IMqttNetLogger logger);
|
||||||
|
|
||||||
Task StopAsync();
|
Task StopAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,19 +26,14 @@ namespace MQTTnet.Adapter
|
|||||||
readonly IMqttChannel _channel;
|
readonly IMqttChannel _channel;
|
||||||
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
readonly MqttPacketInspector _packetInspector;
|
|
||||||
|
|
||||||
readonly byte[] _singleByteBuffer = new byte[1];
|
readonly byte[] _singleByteBuffer = new byte[1];
|
||||||
|
|
||||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||||
|
|
||||||
Statistics _statistics; // mutable struct, don't make readonly!
|
Statistics _statistics; // mutable struct, don't make readonly!
|
||||||
|
|
||||||
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||||
_packetInspector = packetInspector;
|
|
||||||
|
|
||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
|
|
||||||
@@ -50,6 +45,8 @@ namespace MQTTnet.Adapter
|
|||||||
_logger = logger.WithSource(nameof(MqttChannelAdapter));
|
_logger = logger.WithSource(nameof(MqttChannelAdapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AllowPacketFragmentation { get; set; } = true;
|
||||||
|
|
||||||
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
|
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
|
||||||
|
|
||||||
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
|
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
|
||||||
@@ -58,12 +55,12 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
public string Endpoint => _channel.Endpoint;
|
public string Endpoint => _channel.Endpoint;
|
||||||
|
|
||||||
public bool IsReadingPacket { get; private set; }
|
|
||||||
|
|
||||||
public bool IsSecureConnection => _channel.IsSecureConnection;
|
public bool IsSecureConnection => _channel.IsSecureConnection;
|
||||||
|
|
||||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||||
|
|
||||||
|
public MqttPacketInspector PacketInspector { get; set; }
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
@@ -77,12 +74,30 @@ namespace MQTTnet.Adapter
|
|||||||
* block forever. Even a cancellation token is not supported properly.
|
* block forever. Even a cancellation token is not supported properly.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var connectTask = _channel.ConnectAsync(cancellationToken);
|
|
||||||
|
|
||||||
var timeout = new TaskCompletionSource<object>();
|
var timeout = new TaskCompletionSource<object>();
|
||||||
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
|
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
|
||||||
{
|
{
|
||||||
|
var connectTask = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If the timeout is already reached the exception is no longer of interest and
|
||||||
|
// must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException.
|
||||||
|
if (!timeout.Task.IsCompleted)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
|
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
|
||||||
|
|
||||||
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
|
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
|
||||||
{
|
{
|
||||||
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
|
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
|
||||||
@@ -126,7 +141,8 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_packetInspector?.BeginReceivePacket();
|
var localPacketInspector = PacketInspector;
|
||||||
|
localPacketInspector?.BeginReceivePacket();
|
||||||
|
|
||||||
ReceivedMqttPacket receivedPacket;
|
ReceivedMqttPacket receivedPacket;
|
||||||
var receivedPacketTask = ReceiveAsync(cancellationToken);
|
var receivedPacketTask = ReceiveAsync(cancellationToken);
|
||||||
@@ -144,7 +160,10 @@ namespace MQTTnet.Adapter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetInspector?.EndReceivePacket();
|
if (localPacketInspector != null)
|
||||||
|
{
|
||||||
|
await localPacketInspector.EndReceivePacket().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
||||||
|
|
||||||
@@ -180,7 +199,10 @@ namespace MQTTnet.Adapter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetStatistics() => _statistics.Reset();
|
public void ResetStatistics()
|
||||||
|
{
|
||||||
|
_statistics.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -197,18 +219,23 @@ namespace MQTTnet.Adapter
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var packetBuffer = PacketFormatterAdapter.Encode(packet);
|
var packetBuffer = PacketFormatterAdapter.Encode(packet);
|
||||||
_packetInspector?.BeginSendPacket(packetBuffer);
|
|
||||||
|
var localPacketInspector = PacketInspector;
|
||||||
|
if (localPacketInspector != null)
|
||||||
|
{
|
||||||
|
await localPacketInspector.BeginSendPacket(packetBuffer).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
|
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
|
||||||
|
|
||||||
if (packetBuffer.Payload.Count > 0)
|
if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
|
||||||
{
|
{
|
||||||
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
|
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
|
||||||
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
|
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
||||||
@@ -270,7 +297,7 @@ namespace MQTTnet.Adapter
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetInspector?.FillReceiveBuffer(_singleByteBuffer);
|
PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
|
||||||
|
|
||||||
encodedByte = _singleByteBuffer[0];
|
encodedByte = _singleByteBuffer[0];
|
||||||
|
|
||||||
@@ -323,7 +350,7 @@ namespace MQTTnet.Adapter
|
|||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetInspector?.FillReceiveBuffer(buffer);
|
PacketInspector?.FillReceiveBuffer(buffer);
|
||||||
|
|
||||||
var hasRemainingLength = buffer[1] != 0;
|
var hasRemainingLength = buffer[1] != 0;
|
||||||
if (!hasRemainingLength)
|
if (!hasRemainingLength)
|
||||||
@@ -369,9 +396,6 @@ namespace MQTTnet.Adapter
|
|||||||
return ReceivedMqttPacket.Empty;
|
return ReceivedMqttPacket.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsReadingPacket = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fixedHeader = readFixedHeaderResult.FixedHeader;
|
var fixedHeader = readFixedHeaderResult.FixedHeader;
|
||||||
if (fixedHeader.RemainingLength == 0)
|
if (fixedHeader.RemainingLength == 0)
|
||||||
{
|
{
|
||||||
@@ -407,16 +431,11 @@ namespace MQTTnet.Adapter
|
|||||||
bodyOffset += readBytes;
|
bodyOffset += readBytes;
|
||||||
} while (bodyOffset < bodyLength);
|
} while (bodyOffset < bodyLength);
|
||||||
|
|
||||||
_packetInspector?.FillReceiveBuffer(body);
|
PacketInspector?.FillReceiveBuffer(body);
|
||||||
|
|
||||||
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
|
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
|
||||||
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
|
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsReadingPacket = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool WrapAndThrowException(Exception exception)
|
static bool WrapAndThrowException(Exception exception)
|
||||||
{
|
{
|
||||||
@@ -455,7 +474,7 @@ namespace MQTTnet.Adapter
|
|||||||
throw new MqttCommunicationException(exception);
|
throw new MqttCommunicationException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Statistics
|
struct Statistics
|
||||||
{
|
{
|
||||||
public long _bytesReceived;
|
public long _bytesReceived;
|
||||||
public long _bytesSent;
|
public long _bytesSent;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
@@ -12,8 +13,8 @@ namespace MQTTnet.Adapter
|
|||||||
{
|
{
|
||||||
public sealed class MqttPacketInspector
|
public sealed class MqttPacketInspector
|
||||||
{
|
{
|
||||||
readonly MqttNetSourceLogger _logger;
|
|
||||||
readonly AsyncEvent<InspectMqttPacketEventArgs> _asyncEvent;
|
readonly AsyncEvent<InspectMqttPacketEventArgs> _asyncEvent;
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
MemoryStream _receivedPacketBuffer;
|
MemoryStream _receivedPacketBuffer;
|
||||||
|
|
||||||
@@ -21,7 +22,11 @@ namespace MQTTnet.Adapter
|
|||||||
{
|
{
|
||||||
_asyncEvent = asyncEvent ?? throw new ArgumentNullException(nameof(asyncEvent));
|
_asyncEvent = asyncEvent ?? throw new ArgumentNullException(nameof(asyncEvent));
|
||||||
|
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
_logger = logger.WithSource(nameof(MqttPacketInspector));
|
_logger = logger.WithSource(nameof(MqttPacketInspector));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,24 +45,11 @@ namespace MQTTnet.Adapter
|
|||||||
_receivedPacketBuffer?.SetLength(0);
|
_receivedPacketBuffer?.SetLength(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndReceivePacket()
|
public Task BeginSendPacket(MqttPacketBuffer buffer)
|
||||||
{
|
{
|
||||||
if (!_asyncEvent.HasHandlers)
|
if (!_asyncEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
return;
|
return CompletedTask.Instance;
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = _receivedPacketBuffer.ToArray();
|
|
||||||
_receivedPacketBuffer.SetLength(0);
|
|
||||||
|
|
||||||
InspectPacket(buffer, MqttPacketFlowDirection.Inbound);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginSendPacket(MqttPacketBuffer buffer)
|
|
||||||
{
|
|
||||||
if (!_asyncEvent.HasHandlers)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a copy of the actual packet so that the inspector gets no access
|
// Create a copy of the actual packet so that the inspector gets no access
|
||||||
@@ -65,7 +57,20 @@ namespace MQTTnet.Adapter
|
|||||||
// intended for debugging etc. so that this is OK.
|
// intended for debugging etc. so that this is OK.
|
||||||
var bufferCopy = buffer.ToArray();
|
var bufferCopy = buffer.ToArray();
|
||||||
|
|
||||||
InspectPacket(bufferCopy, MqttPacketFlowDirection.Outbound);
|
return InspectPacket(bufferCopy, MqttPacketFlowDirection.Outbound);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task EndReceivePacket()
|
||||||
|
{
|
||||||
|
if (!_asyncEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = _receivedPacketBuffer.ToArray();
|
||||||
|
_receivedPacketBuffer.SetLength(0);
|
||||||
|
|
||||||
|
return InspectPacket(buffer, MqttPacketFlowDirection.Inbound);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FillReceiveBuffer(byte[] buffer)
|
public void FillReceiveBuffer(byte[] buffer)
|
||||||
@@ -78,12 +83,12 @@ namespace MQTTnet.Adapter
|
|||||||
_receivedPacketBuffer?.Write(buffer, 0, buffer.Length);
|
_receivedPacketBuffer?.Write(buffer, 0, buffer.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectPacket(byte[] buffer, MqttPacketFlowDirection direction)
|
async Task InspectPacket(byte[] buffer, MqttPacketFlowDirection direction)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
|
var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
|
||||||
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
|
await _asyncEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -11,8 +12,9 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException
|
public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException
|
||||||
{
|
{
|
||||||
public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
|
public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket, Exception innerExcpetion = null) : base(
|
||||||
: base($"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.")
|
$"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.",
|
||||||
|
innerExcpetion)
|
||||||
{
|
{
|
||||||
ReasonCode = disconnectPacket.ReasonCode;
|
ReasonCode = disconnectPacket.ReasonCode;
|
||||||
SessionExpiryInterval = disconnectPacket.SessionExpiryInterval;
|
SessionExpiryInterval = disconnectPacket.SessionExpiryInterval;
|
||||||
@@ -23,12 +25,12 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public MqttDisconnectReasonCode? ReasonCode { get; }
|
public MqttDisconnectReasonCode? ReasonCode { get; }
|
||||||
|
|
||||||
public uint? SessionExpiryInterval { get; }
|
|
||||||
|
|
||||||
public string ReasonString { get; }
|
public string ReasonString { get; }
|
||||||
|
|
||||||
public List<MqttUserProperty> UserProperties { get; }
|
|
||||||
|
|
||||||
public string ServerReference { get; }
|
public string ServerReference { get; }
|
||||||
|
|
||||||
|
public uint? SessionExpiryInterval { get; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||||
|
|
||||||
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
||||||
|
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client.Internal
|
||||||
|
{
|
||||||
|
public sealed class MqttClientEvents
|
||||||
|
{
|
||||||
|
public AsyncEvent<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceivedEvent { get; } = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||||
|
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||||
|
public AsyncEvent<MqttClientConnectingEventArgs> ConnectingEvent { get; } = new AsyncEvent<MqttClientConnectingEventArgs>();
|
||||||
|
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||||
|
public AsyncEvent<InspectMqttPacketEventArgs> InspectPacketEvent { get; } = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user