Compare commits

..
Author SHA1 Message Date
Christian 25b42df85c Refactor code 2023-01-19 20:53:57 +01:00
Christian f8d614e71f Merge branch 'master' into Managed-Client-V2 2023-01-19 20:50:34 +01:00
Christian 7bb29d3c92 Merge master 2022-12-28 14:15:27 +01:00
Christian 4d81d3f3d9 Merge branch 'master' into Managed-Client-V2 2022-12-28 14:15:21 +01:00
Christian 3937aa76e3 Fix merge conflicts 2022-11-13 18:41:28 +01:00
Christian 2090b08547 Merge branch 'master' into Managed-Client-V2 2022-11-13 18:13:23 +01:00
Christian 7c3fad6147 Implement new managed client. 2022-09-29 18:38:29 +02:00
191 changed files with 3380 additions and 5075 deletions
+8 -5
View File
@@ -1,5 +1,8 @@
* [Client] Exposed the _EndPoint_ type to support other endpoint types (like Unix Domain Sockets) in client options (#1919).
* [ManagedClient] Added support for intercepting (and cancelling) publish messages (#1915, thanks to @hannasm).
* [Server] Added new events for delivered and dropped messages (#1866, thanks to @kallayj).
* [Server] The server will no longer treat a client which is receiving a large payload as alive. The packet must be received completely within the keep alive boundaries (BREAKING CHANGE!, #1883).
* [Server] Fixed "service not registered" exception in ASP.NET integration (#1889).
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
* [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).
+2 -2
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.3.3.${{github.run_number}}"
VERSION: "4.2.0.${{github.run_number}}"
jobs:
build:
@@ -54,7 +54,7 @@ jobs:
uses: darenm/Setup-VSTest@v1
- name: Core Tests
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net7.0\MQTTnet.Tests.dll
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
- name: ASP.NET Tests
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
+69 -3
View File
@@ -1,4 +1,67 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=MQTTnet/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="MQTTnet"&gt;&lt;CppReformatCode&gt;True&lt;/CppReformatCode&gt;&lt;ShaderLabReformatCode&gt;True&lt;/ShaderLabReformatCode&gt;&lt;XMLReformatCode&gt;True&lt;/XMLReformatCode&gt;&lt;HtmlReformatCode&gt;True&lt;/HtmlReformatCode&gt;&lt;VBReformatCode&gt;True&lt;/VBReformatCode&gt;&lt;CSReformatCode&gt;True&lt;/CSReformatCode&gt;&lt;CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /&gt;&lt;CSArrangeQualifiers&gt;True&lt;/CSArrangeQualifiers&gt;&lt;CSFixBuiltinTypeReferences&gt;True&lt;/CSFixBuiltinTypeReferences&gt;&lt;CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;CppUpdateFileHeader&gt;True&lt;/CppUpdateFileHeader&gt;&lt;IDEA_SETTINGS&gt;&amp;lt;profile version="1.0"&amp;gt;&#xD;
&amp;lt;option name="myName" value="MQTTnet" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="TypeScriptExplicitMemberType" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="true" level="WARNING" enabled_by_default="true" /&amp;gt;&#xD;
&amp;lt;/profile&amp;gt;&lt;/IDEA_SETTINGS&gt;&lt;RIDER_SETTINGS&gt;&amp;lt;profile&amp;gt;&#xD;
&amp;lt;Language id="CSS"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="EditorConfig"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="HTML"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;&#xD;
&amp;lt;OptimizeImports&amp;gt;true&amp;lt;/OptimizeImports&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="HTTP Request"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="Handlebars"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="Ini"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="JSON"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="Jade"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="JavaScript"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;&#xD;
&amp;lt;OptimizeImports&amp;gt;true&amp;lt;/OptimizeImports&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="Markdown"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="Properties"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="RELAX-NG"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="SQL"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="XML"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;Rearrange&amp;gt;true&amp;lt;/Rearrange&amp;gt;&#xD;
&amp;lt;OptimizeImports&amp;gt;true&amp;lt;/OptimizeImports&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;Language id="yaml"&amp;gt;&#xD;
&amp;lt;Reformat&amp;gt;true&amp;lt;/Reformat&amp;gt;&#xD;
&amp;lt;/Language&amp;gt;&#xD;
&amp;lt;/profile&amp;gt;&lt;/RIDER_SETTINGS&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;/CSOptimizeUsings&gt;&lt;CSShortenReferences&gt;True&lt;/CSShortenReferences&gt;&lt;VBOptimizeImports&gt;True&lt;/VBOptimizeImports&gt;&lt;VBShortenReferences&gt;True&lt;/VBShortenReferences&gt;&lt;Xaml.RemoveRedundantNamespaceAlias&gt;True&lt;/Xaml.RemoveRedundantNamespaceAlias&gt;&lt;AspOptimizeRegisterDirectives&gt;True&lt;/AspOptimizeRegisterDirectives&gt;&lt;CSReorderTypeMembers&gt;True&lt;/CSReorderTypeMembers&gt;&lt;RemoveCodeRedundancies&gt;True&lt;/RemoveCodeRedundancies&gt;&lt;CSUseAutoProperty&gt;True&lt;/CSUseAutoProperty&gt;&lt;CSMakeFieldReadonly&gt;True&lt;/CSMakeFieldReadonly&gt;&lt;CSMakeAutoPropertyGetOnly&gt;True&lt;/CSMakeAutoPropertyGetOnly&gt;&lt;CppAddTypenameTemplateKeywords&gt;True&lt;/CppAddTypenameTemplateKeywords&gt;&lt;CppJoinDeclarationAndAssignmentDescriptor&gt;True&lt;/CppJoinDeclarationAndAssignmentDescriptor&gt;&lt;CppMakeLocalVarConstDescriptor&gt;True&lt;/CppMakeLocalVarConstDescriptor&gt;&lt;CppMakeMethodConst&gt;True&lt;/CppMakeMethodConst&gt;&lt;CppMakeMethodStatic&gt;True&lt;/CppMakeMethodStatic&gt;&lt;CppRemoveCastDescriptor&gt;True&lt;/CppRemoveCastDescriptor&gt;&lt;CppRemoveElseKeyword&gt;True&lt;/CppRemoveElseKeyword&gt;&lt;CppRemoveElaboratedTypeSpecifierDescriptor&gt;True&lt;/CppRemoveElaboratedTypeSpecifierDescriptor&gt;&lt;CppRemoveRedundantLambdaParameterListDescriptor&gt;True&lt;/CppRemoveRedundantLambdaParameterListDescriptor&gt;&lt;CppRemoveRedundantMemberInitializerDescriptor&gt;True&lt;/CppRemoveRedundantMemberInitializerDescriptor&gt;&lt;CppRemoveRedundantParentheses&gt;True&lt;/CppRemoveRedundantParentheses&gt;&lt;CppShortenQualifiedName&gt;True&lt;/CppShortenQualifiedName&gt;&lt;CppDeleteRedundantSpecifier&gt;True&lt;/CppDeleteRedundantSpecifier&gt;&lt;CppRemoveStatement&gt;True&lt;/CppRemoveStatement&gt;&lt;CppRemoveTemplateArgumentsDescriptor&gt;True&lt;/CppRemoveTemplateArgumentsDescriptor&gt;&lt;CppDeleteRedundantTypenameTemplateKeywords&gt;True&lt;/CppDeleteRedundantTypenameTemplateKeywords&gt;&lt;CppRemoveUnreachableCode&gt;True&lt;/CppRemoveUnreachableCode&gt;&lt;CppRemoveUnusedIncludes&gt;True&lt;/CppRemoveUnusedIncludes&gt;&lt;CppRemoveUnusedLambdaCaptures&gt;True&lt;/CppRemoveUnusedLambdaCaptures&gt;&lt;CppCStyleToStaticCastDescriptor&gt;True&lt;/CppCStyleToStaticCastDescriptor&gt;&lt;CppReplaceExpressionWithBooleanConst&gt;True&lt;/CppReplaceExpressionWithBooleanConst&gt;&lt;CppMakeIfConstexpr&gt;True&lt;/CppMakeIfConstexpr&gt;&lt;CppMakePostfixOperatorPrefix&gt;True&lt;/CppMakePostfixOperatorPrefix&gt;&lt;CppChangeSmartPointerToMakeFunction&gt;True&lt;/CppChangeSmartPointerToMakeFunction&gt;&lt;CppReplaceThrowWithRethrowFix&gt;True&lt;/CppReplaceThrowWithRethrowFix&gt;&lt;CppTypeTraitAliasDescriptor&gt;True&lt;/CppTypeTraitAliasDescriptor&gt;&lt;CppReplaceExpressionWithNullptr&gt;True&lt;/CppReplaceExpressionWithNullptr&gt;&lt;CppReplaceTieWithStructuredBindingDescriptor&gt;True&lt;/CppReplaceTieWithStructuredBindingDescriptor&gt;&lt;CppUseAssociativeContainsDescriptor&gt;True&lt;/CppUseAssociativeContainsDescriptor&gt;&lt;CppUseEraseAlgorithmDescriptor&gt;True&lt;/CppUseEraseAlgorithmDescriptor&gt;&lt;RemoveCodeRedundanciesVB&gt;True&lt;/RemoveCodeRedundanciesVB&gt;&lt;Xaml.RedundantFreezeAttribute&gt;True&lt;/Xaml.RedundantFreezeAttribute&gt;&lt;Xaml.RemoveRedundantModifiersAttribute&gt;True&lt;/Xaml.RemoveRedundantModifiersAttribute&gt;&lt;Xaml.RemoveRedundantNameAttribute&gt;True&lt;/Xaml.RemoveRedundantNameAttribute&gt;&lt;Xaml.RemoveRedundantResource&gt;True&lt;/Xaml.RemoveRedundantResource&gt;&lt;Xaml.RemoveRedundantCollectionProperty&gt;True&lt;/Xaml.RemoveRedundantCollectionProperty&gt;&lt;Xaml.RemoveRedundantAttachedPropertySetter&gt;True&lt;/Xaml.RemoveRedundantAttachedPropertySetter&gt;&lt;Xaml.RemoveRedundantStyledValue&gt;True&lt;/Xaml.RemoveRedundantStyledValue&gt;&lt;Xaml.RemoveForbiddenResourceName&gt;True&lt;/Xaml.RemoveForbiddenResourceName&gt;&lt;Xaml.RemoveRedundantGridDefinitionsAttribute&gt;True&lt;/Xaml.RemoveRedundantGridDefinitionsAttribute&gt;&lt;Xaml.RemoveRedundantUpdateSourceTriggerAttribute&gt;True&lt;/Xaml.RemoveRedundantUpdateSourceTriggerAttribute&gt;&lt;Xaml.RemoveRedundantBindingModeAttribute&gt;True&lt;/Xaml.RemoveRedundantBindingModeAttribute&gt;&lt;Xaml.RemoveRedundantGridSpanAttribut&gt;True&lt;/Xaml.RemoveRedundantGridSpanAttribut&gt;&lt;CSharpFormatDocComments&gt;True&lt;/CSharpFormatDocComments&gt;&lt;VBFormatDocComments&gt;True&lt;/VBFormatDocComments&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
@@ -8,23 +71,28 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INVOCATION_PARENS_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_PROPERTY_PATTERNS_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_SWITCH_EXPRESSION_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_PROPERTY_PATTERN_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_CALL_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">180</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PROPERTY_PATTERN/@EntryValue">CHOP_ALWAYS</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/KeepUserLineBreaks/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
@@ -230,7 +298,6 @@
<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.
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_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
@@ -241,7 +308,6 @@ 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_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/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/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
-16
View File
@@ -50,22 +50,6 @@ 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
* 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
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+21 -99
View File
@@ -7,7 +7,6 @@
// ReSharper disable InconsistentNaming
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
@@ -136,15 +135,15 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
.WithTlsOptions(
.WithTls(
o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.WithCertificateValidationHandler(_ => true);
o.CertificateValidationHandler = _ => true;
// The default value is determined by the OS. Set manually to force version.
o.WithSslProtocols(SslProtocols.Tls12);
o.SslProtocol = SslProtocols.Tls12;
})
.Build();
@@ -171,7 +170,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -193,7 +192,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -216,11 +215,13 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions(
o => o.WithCertificateValidationHandler(
.WithTls(
o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
_ => true))
o.CertificateValidationHandler = _ => true;
})
.Build();
// In MQTTv5 the response contains much more information.
@@ -235,31 +236,6 @@ 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()
{
/*
@@ -315,19 +291,18 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTlsOptions(
.WithTls(
o =>
{
o.WithCertificateValidationHandler(
eventArgs =>
{
eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole();
return true;
});
o.CertificateValidationHandler = eventArgs =>
{
eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole();
return true;
};
})
.Build();
@@ -431,59 +406,6 @@ 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,7 +5,6 @@
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
using MQTTnet.Client;
using MQTTnet.Packets;
@@ -177,62 +176,4 @@ public static class Client_Subscribe_Samples
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")
.Build();
mqttClient.InspectPacketAsync += OnInspectPacket;
mqttClient.InspectPackage += OnInspectPackage;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
}
}
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
{
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
{
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@@ -44,9 +44,9 @@ public sealed class Managed_Client_Simple_Samples
Console.WriteLine("The managed MQTT client is connected.");
// Wait until the queue is fully processed.
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
SpinWait.SpinUntil(() => managedMqttClient.EnqueuedApplicationMessagesCount == 0, 10000);
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
Console.WriteLine($"Pending messages = {managedMqttClient.EnqueuedApplicationMessagesCount}");
}
}
}
+1 -18
View File
@@ -4,13 +4,7 @@
using System.Reflection;
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("Welcome to MQTTnet samples!");
Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
@@ -22,11 +16,7 @@ foreach (var sampleClass in sampleClasses)
index++;
}
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0");
var selectedSampleClass = sampleClasses[selectedIndex];
@@ -39,19 +29,12 @@ foreach (var sampleMethod in sampleMethods)
index++;
}
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor();
input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0");
var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Executing sample...");
Console.ResetColor();
Console.WriteLine();
try
@@ -17,7 +17,7 @@ using MQTTnet.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()
{
@@ -7,8 +7,6 @@
// ReSharper disable InconsistentNaming
using System.Text.Json;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.Server;
@@ -34,10 +32,7 @@ public static class Server_Retained_Messages_Samples
{
try
{
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages;
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
Console.WriteLine("Retained messages loaded.");
}
catch (FileNotFoundException)
@@ -61,9 +56,7 @@ 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
// of retained messages must be provided in the loaded event.
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
@@ -86,59 +79,4 @@ public static class Server_Retained_Messages_Samples
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
};
}
}
}
-75
View File
@@ -1,75 +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.
// 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,6 +48,7 @@ namespace MQTTnet.AspNetCore.Tests
{
var mockup = new ConnectionHandlerMockup();
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
.UseStartup<Startup>()
.ConfigureServices(
@@ -102,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync();
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 buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
@@ -6,6 +6,7 @@ using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Formatter;
using System;
using System.Net;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
@@ -21,7 +22,8 @@ namespace MQTTnet.AspNetCore.Client
{
case MqttClientTcpOptions tcpOptions:
{
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
var endpoint = new DnsEndPoint(tcpOptions.Server, tcpOptions.GetPort());
var tcpConnection = new TcpConnection(endpoint);
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection);
@@ -15,15 +15,15 @@ using System.Threading.Tasks;
namespace MQTTnet.AspNetCore.Client.Tcp
{
public sealed class SocketConnection : ConnectionContext
public class TcpConnection : ConnectionContext
{
volatile bool _aborted;
readonly EndPoint _endPoint;
SocketSender _sender;
SocketReceiver _receiver;
private volatile bool _aborted;
private readonly EndPoint _endPoint;
private SocketSender _sender;
private SocketReceiver _receiver;
Socket _socket;
IDuplexPipe _application;
private Socket _socket;
private IDuplexPipe _application;
public bool IsConnected { get; private 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 IDuplexPipe Transport { get; set; }
public SocketConnection(EndPoint endPoint)
public TcpConnection(EndPoint endPoint)
{
_endPoint = endPoint;
}
public SocketConnection(Socket socket)
public TcpConnection(Socket socket)
{
_socket = socket;
_endPoint = socket.RemoteEndPoint;
@@ -87,7 +87,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
IsConnected = true;
}
async Task ExecuteAsync()
private async Task ExecuteAsync()
{
Exception sendError = null;
try
@@ -114,7 +114,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected exception in {nameof(SocketConnection)}.{nameof(StartAsync)}: " + ex);
Console.WriteLine($"Unexpected exception in {nameof(TcpConnection)}.{nameof(StartAsync)}: " + ex);
}
finally
{
@@ -122,8 +122,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_application.Input.Complete(sendError);
}
}
async Task DoReceive()
private async Task DoReceive()
{
Exception error = null;
@@ -172,7 +171,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
}
}
async Task ProcessReceives()
private async Task ProcessReceives()
{
while (true)
{
@@ -205,12 +204,12 @@ namespace MQTTnet.AspNetCore.Client.Tcp
}
}
Exception ConnectionAborted()
private Exception ConnectionAborted()
{
return new MqttCommunicationException("Connection Aborted");
}
async Task<Exception> DoSend()
private async Task<Exception> DoSend()
{
Exception error = null;
@@ -241,7 +240,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
return error;
}
async Task ProcessSends()
private async Task ProcessSends()
{
while (true)
{
@@ -33,11 +33,8 @@ namespace MQTTnet.AspNetCore
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
if (!(_connection is SocketConnection tcp) || tcp.IsConnected)
{
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
public long BytesReceived { get; private set; }
@@ -83,6 +80,8 @@ namespace MQTTnet.AspNetCore
}
}
public bool IsReadingPacket { get; private set; }
public bool IsSecureConnection
{
get
@@ -109,7 +108,7 @@ namespace MQTTnet.AspNetCore
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (_connection is SocketConnection tcp && !tcp.IsConnected)
if (_connection is TcpConnection tcp && !tcp.IsConnected)
{
await tcp.StartAsync().ConfigureAwait(false);
}
@@ -162,6 +161,11 @@ namespace MQTTnet.AspNetCore
BytesReceived += received;
return packet;
}
else
{
// we did receive something but the message is not yet complete
IsReadingPacket = true;
}
}
else if (readResult.IsCompleted)
{
@@ -182,9 +186,12 @@ namespace MQTTnet.AspNetCore
// completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(exception);
_output?.Complete(exception);
throw;
}
finally
{
IsReadingPacket = false;
}
cancellationToken.ThrowIfCancellationRequested();
return null;
+6 -14
View File
@@ -2,7 +2,6 @@
// 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.Threading;
using System.Threading.Tasks;
@@ -15,27 +14,20 @@ namespace MQTTnet.AspNetCore
{
public sealed class MqttHostedServer : MqttServer, IHostedService
{
readonly MqttFactory _mqttFactory;
public MqttHostedServer(MqttFactory mqttFactory, MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
options,
adapters,
logger)
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
: base(options, adapters, logger)
{
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
}
public async Task StartAsync(CancellationToken cancellationToken)
public 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)
{
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
return StopAsync();
}
}
}
@@ -2,34 +2,38 @@
// 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.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
namespace MQTTnet.AspNetCore
{
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
IMqttNetLogger _logger = new MqttNetNullLogger();
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
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;
}
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}";
@@ -44,7 +48,7 @@ namespace MQTTnet.AspNetCore
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
{
await clientHandler(channelAdapter).ConfigureAwait(false);
}
@@ -56,15 +60,8 @@ namespace MQTTnet.AspNetCore
}
}
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;
}
}
}
@@ -4,7 +4,6 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
@@ -15,92 +14,6 @@ namespace MQTTnet.AspNetCore
{
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));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
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)
{
if (serviceCollection is null)
@@ -110,14 +23,57 @@ namespace MQTTnet.AspNetCore
serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure);
return serviceCollection;
}
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
{
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
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 = 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;
}
@@ -129,5 +85,21 @@ namespace MQTTnet.AspNetCore
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);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
static byte[] Join(params ArraySegment<byte>[] chunks)
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload);
writer.WriteBinaryData(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinary(_demoPayload);
writer.WriteBinaryData(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
writer.WriteBinaryData(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload);
writer.WriteBinaryData(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
writer.WriteBinaryData(_demoPayload);
writer.Reset(0);
}
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
public void Deserialize_10000_Messages()
{
var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
for (var i = 0; i < 10000; i++)
{
@@ -29,12 +29,12 @@ namespace MQTTnet.Benchmarks
var task = Task.Run(() => server.AcceptSocket());
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
var clientConnection = new TcpConnection(new IPEndPoint(IPAddress.Loopback, 1883));
clientConnection.StartAsync().GetAwaiter().GetResult();
_client = clientConnection.Transport;
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
var serverConnection = new TcpConnection(task.GetAwaiter().GetResult());
serverConnection.StartAsync().GetAwaiter().GetResult();
_server = serverConnection.Transport;
}
@@ -1,52 +1,56 @@
// 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.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient
{
public interface IManagedMqttClient : IDisposable
{
event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync;
event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync;
event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync;
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
event Func<EventArgs, Task> ConnectionStateChangedAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
int EnqueuedApplicationMessagesCount { get; }
IMqttClient InternalClient { get; }
bool IsConnected { get; }
bool IsStarted { get; }
ManagedMqttClientOptions Options { get; }
int PendingApplicationMessagesCount { get; }
Task EnqueueAsync(MqttApplicationMessage applicationMessage);
Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage);
Task PingAsync(CancellationToken cancellationToken = default);
Task StartAsync(ManagedMqttClientOptions options);
Task StopAsync(bool cleanDisconnect = true);
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
Task UnsubscribeAsync(ICollection<string> topics);
Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
Task SubscribeAsync(MqttClientSubscribeOptions options);
Task UnsubscribeAsync(MqttClientUnsubscribeOptions options);
}
}
@@ -2,9 +2,10 @@
<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' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -0,0 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Cevents/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Coptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -6,10 +6,16 @@ using System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttApplicationMessage
public sealed class ManagedMqttApplicationMessage
{
public Guid Id { get; set; } = Guid.NewGuid();
public ManagedMqttApplicationMessage(string id, MqttApplicationMessage applicationMessage)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public string Id { get; }
public MqttApplicationMessage ApplicationMessage { get; set; }
public MqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -6,15 +6,19 @@ using System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttApplicationMessageBuilder
public sealed class ManagedMqttApplicationMessageBuilder
{
private Guid _id = Guid.NewGuid();
private MqttApplicationMessage _applicationMessage;
MqttApplicationMessage _applicationMessage;
string _id = Guid.NewGuid().ToString();
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
public ManagedMqttApplicationMessage Build()
{
_id = id;
return this;
if (_applicationMessage == null)
{
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
}
return new ManagedMqttApplicationMessage(_id, _applicationMessage);
}
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
@@ -25,7 +29,10 @@ namespace MQTTnet.Extensions.ManagedClient
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var internalBuilder = new MqttApplicationMessageBuilder();
builder(internalBuilder);
@@ -34,18 +41,16 @@ namespace MQTTnet.Extensions.ManagedClient
return this;
}
public ManagedMqttApplicationMessage Build()
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
{
if (_applicationMessage == null)
{
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
}
_id = id.ToString();
return this;
}
return new ManagedMqttApplicationMessage
{
Id = _id,
ApplicationMessage = _applicationMessage
};
public ManagedMqttApplicationMessageBuilder WithId(string id)
{
_id = id ?? throw new ArgumentNullException(nameof(id));
return this;
}
}
}
}
@@ -17,11 +17,10 @@ using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
public sealed class ManagedMqttClient : Disposable
{
readonly AsyncEvent<InterceptingPublishMessageEventArgs> _interceptingPublishMessageEvent = new AsyncEvent<InterceptingPublishMessageEventArgs>();
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
readonly AsyncEvent<ApplicationMessageDroppedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
@@ -77,7 +76,7 @@ namespace MQTTnet.Extensions.ManagedClient
_logger = logger.WithSource(nameof(ManagedMqttClient));
}
public event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageSkippedAsync
{
add => _applicationMessageSkippedEvent.AddHandler(value);
remove => _applicationMessageSkippedEvent.RemoveHandler(value);
@@ -89,14 +88,6 @@ namespace MQTTnet.Extensions.ManagedClient
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
}
public event Func<InterceptingPublishMessageEventArgs, Task> InterceptPublishMessageAsync
{
add => _interceptingPublishMessageEvent.AddHandler(value);
remove => _interceptingPublishMessageEvent.RemoveHandler(value);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => InternalClient.ApplicationMessageReceivedAsync += value;
@@ -127,12 +118,18 @@ namespace MQTTnet.Extensions.ManagedClient
remove => InternalClient.DisconnectedAsync -= value;
}
public event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
public event Func<ManagedProcessFailedEventArgs, Task> SubscribedAsync
{
add => _synchronizingSubscriptionsFailedEvent.AddHandler(value);
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> ApplicationMessageEnqueued;
public IMqttClient InternalClient { get; }
public bool IsConnected => InternalClient.IsConnected;
@@ -173,7 +170,7 @@ namespace MQTTnet.Extensions.ManagedClient
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
ManagedMqttApplicationMessage removedMessage = null;
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
ApplicationMessageDroppedEventArgs applicationMessageDroppedEventArgs = null;
try
{
@@ -184,7 +181,7 @@ namespace MQTTnet.Extensions.ManagedClient
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{
_logger.Verbose("Skipping publish of new application message because internal queue is full.");
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(applicationMessage);
return;
}
@@ -192,7 +189,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
removedMessage = _messageQueue.RemoveFirst();
_logger.Verbose("Removed oldest application message from internal queue because it is full.");
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(removedMessage);
}
}
@@ -211,9 +208,9 @@ namespace MQTTnet.Extensions.ManagedClient
}
finally
{
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
if (applicationMessageDroppedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
{
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageDroppedEventArgs).ConfigureAwait(false);
}
}
}
@@ -362,13 +359,13 @@ namespace MQTTnet.Extensions.ManagedClient
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
}
async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
async Task HandleSubscriptionExceptionAsync(Exception exception)
{
_logger.Warning(exception, "Synchronizing subscriptions failed.");
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
{
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
}
}
@@ -466,13 +463,11 @@ namespace MQTTnet.Extensions.ManagedClient
{
_logger.Info("Publishing subscriptions at reconnect");
List<MqttTopicFilter> topicFilters = null;
try
{
if (_reconnectSubscriptions.Any())
{
topicFilters = new List<MqttTopicFilter>();
var topicFilters = new List<MqttTopicFilter>();
foreach (var sub in _reconnectSubscriptions)
{
@@ -490,7 +485,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
}
}
@@ -602,9 +597,6 @@ namespace MQTTnet.Extensions.ManagedClient
}
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
}
if (addedSubscriptions != null && addedSubscriptions.Any())
@@ -621,7 +613,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
}
}
@@ -709,20 +701,9 @@ namespace MQTTnet.Extensions.ManagedClient
async Task TryPublishQueuedMessageAsync(ManagedMqttApplicationMessage message)
{
Exception transmitException = null;
bool acceptPublish = true;
try
{
if (_interceptingPublishMessageEvent.HasHandlers)
{
var interceptEventArgs = new InterceptingPublishMessageEventArgs(message);
await _interceptingPublishMessageEvent.InvokeAsync(interceptEventArgs).ConfigureAwait(false);
acceptPublish = interceptEventArgs.AcceptPublish;
}
if (acceptPublish)
{
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
}
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{
@@ -3,8 +3,9 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -38,6 +39,35 @@ namespace MQTTnet.Extensions.ManagedClient
return managedMqttClient.EnqueueAsync(applicationMessage);
}
public static Task StopAsync(this IManagedMqttClient managedMqttClient, bool isClean = true, CancellationToken cancellationToken = default)
{
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
return managedMqttClient.StopAsync(new MqttClientDisconnectOptions
{
IsClean = isClean
}, cancellationToken);
}
public static Task SubscribeAsync(this IManagedMqttClient managedMqttClient, MqttTopicFilter topicFilter)
{
if (managedMqttClient == null)
{
throw new ArgumentNullException(nameof(managedMqttClient));
}
if (topicFilter == null)
{
throw new ArgumentNullException(nameof(topicFilter));
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
return managedMqttClient.SubscribeAsync(subscribeOptions);
}
public static Task SubscribeAsync(
this IManagedMqttClient managedMqttClient,
string topic,
@@ -53,11 +83,8 @@ namespace MQTTnet.Extensions.ManagedClient
throw new ArgumentNullException(nameof(topic));
}
return managedMqttClient.SubscribeAsync(
new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
});
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build();
return managedMqttClient.SubscribeAsync(subscribeOptions);
}
public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic)
@@ -72,7 +99,8 @@ namespace MQTTnet.Extensions.ManagedClient
throw new ArgumentNullException(nameof(topic));
}
return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
return managedMqttClient.UnsubscribeAsync(unsubscribeOptions);
}
}
}
@@ -1,34 +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 MQTTnet.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedProcessFailedEventArgs : EventArgs
{
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{
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 List<string> AddedSubscriptions { get; }
public List<string> RemovedSubscriptions { get; }
}
}
@@ -10,6 +10,21 @@ namespace MQTTnet.Extensions.ManagedClient
{
public static class MqttFactoryExtensions
{
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient, IMqttNetLogger logger)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new NewManagedMqttClient(factory.CreateMqttClient(), logger);
}
return new NewManagedMqttClient(mqttClient, logger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{
if (factory == null)
@@ -19,10 +34,10 @@ namespace MQTTnet.Extensions.ManagedClient
if (mqttClient == null)
{
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
return new NewManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
}
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
return new NewManagedMqttClient(mqttClient, factory.DefaultLogger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
@@ -37,10 +52,10 @@ namespace MQTTnet.Extensions.ManagedClient
throw new ArgumentNullException(nameof(logger));
}
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
return new NewManagedMqttClient(factory.CreateMqttClient(logger), logger);
}
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory factory)
{
return new ManagedMqttClientOptionsBuilder();
}
@@ -0,0 +1,95 @@
// 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.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ApplicationMessagesManager
{
readonly MqttNetSourceLogger _logger;
readonly Queue<ManagedMqttApplicationMessage> _messageQueue = new Queue<ManagedMqttApplicationMessage>();
readonly IMqttClient _mqttClient;
public ApplicationMessagesManager(IMqttClient mqttClient, IMqttNetLogger logger)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource<ApplicationMessagesManager>();
}
public AsyncEvent<ApplicationMessageDroppedEventArgs> ApplicationMessageDroppedEvent { get; } = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
public AsyncEvent<ApplicationMessageEnqueueingEventArgs> ApplicationMessageEnqueueingEvent { get; } = new AsyncEvent<ApplicationMessageEnqueueingEventArgs>();
public int EnqueuedApplicationMessagesCount
{
get
{
lock (_messageQueue)
{
return _messageQueue.Count;
}
}
}
public void Dequeue()
{
lock (_messageQueue)
{
_messageQueue.Dequeue();
}
}
public async Task Enqueue(ManagedMqttApplicationMessage applicationMessage)
{
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
// Fire the event first so that the handler can make sure that this message is
// properly backed up in a suitable storage.
if (ApplicationMessageEnqueueingEvent.HasHandlers)
{
var eventArgs = new ApplicationMessageEnqueueingEventArgs(applicationMessage, 0);
await ApplicationMessageEnqueueingEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
lock (_messageQueue)
{
// TODO: Drop if too much!
_messageQueue.Enqueue(applicationMessage);
}
}
public bool TryPeek(out ManagedMqttApplicationMessage applicationMessage)
{
lock (_messageQueue)
{
if (_messageQueue.Count == 0)
{
applicationMessage = null;
return false;
}
applicationMessage = _messageQueue.Peek();
return true;
}
}
}
}
@@ -6,13 +6,13 @@ using System;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ApplicationMessageSkippedEventArgs : EventArgs
public sealed class ApplicationMessageDroppedEventArgs : EventArgs
{
public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
}
}
}
@@ -6,15 +6,16 @@ using System;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class InterceptingPublishMessageEventArgs : EventArgs
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
{
public InterceptingPublishMessageEventArgs(ManagedMqttApplicationMessage applicationMessage)
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PacketIdentifier = packetIdentifier;
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
public bool AcceptPublish { get; set; } = true;
public ushort PacketIdentifier { get; }
}
}
}
@@ -15,10 +15,12 @@ namespace MQTTnet.Extensions.ManagedClient
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Then this is _null_ the message was processed successfully without any error.
/// Gets or sets the exception which was thrown while sending the message.
/// </summary>
public Exception Exception { get; }
public bool HasFailed => Exception != null;
}
}
}
@@ -1,3 +1,7 @@
// 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 MQTTnet.Client;
@@ -12,7 +16,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
/// <summary>
/// This is null when the connection was failing and the server was not reachable.
/// This is null when the server was not reachable and thus no MQTT response was transmitted.
/// </summary>
public MqttClientConnectResult ConnectResult { 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 System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedProcessFailedEventArgs : EventArgs
{
public ManagedProcessFailedEventArgs(Exception exception)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
}
public Exception Exception { get; }
}
}
@@ -0,0 +1,27 @@
// 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;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class SubscribeProcessedEventArgs : EventArgs
{
public SubscribeProcessedEventArgs(MqttClientSubscribeOptions options, MqttClientSubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
Result = result;
UserProperties = userProperties;
}
public MqttClientSubscribeOptions Options { get; }
public MqttClientSubscribeResultCode Result { get; }
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
}
}
@@ -0,0 +1,27 @@
// 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;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class UnsubscribeProcessedEventArgs : EventArgs
{
public UnsubscribeProcessedEventArgs(MqttClientUnsubscribeOptions options, MqttClientUnsubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
Result = result;
UserProperties = userProperties;
}
public MqttClientUnsubscribeOptions Options { get; }
public MqttClientUnsubscribeResultCode Result { get; }
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
}
}
@@ -2,12 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Protocol
namespace MQTTnet.Extensions.ManagedClient
{
public static class MqttPorts
public enum MaintainConnectionResult
{
public const int Default = 1883;
NoChange,
public const int Secure = 8883;
ConnectedEstablished,
NotConnected
}
}
@@ -0,0 +1,35 @@
// 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.Client;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttSubscription
{
public ManagedMqttSubscription(MqttClientSubscribeOptions subscribeOptions)
{
SubscribeOptions = subscribeOptions ?? throw new ArgumentNullException(nameof(subscribeOptions));
Topic = subscribeOptions.TopicFilters.First().Topic;
}
public bool IsObsolete => UnsubscribeOptions != null;
public bool IsPending { get; set; } = true;
public MqttClientSubscribeOptions SubscribeOptions { get; }
public string Topic { get; }
public MqttClientUnsubscribeOptions UnsubscribeOptions { get; private set; }
public void MarkAsUnsubscribed(MqttClientUnsubscribeOptions options)
{
UnsubscribeOptions = options ?? throw new ArgumentNullException(nameof(options));
}
}
}
@@ -0,0 +1,155 @@
// 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.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttSubscriptionsManager
{
readonly MqttNetSourceLogger _logger;
readonly IMqttClient _mqttClient;
// Do not use a dictionary in order to preserve subscription ordering (retained messages).
readonly List<ManagedMqttSubscription> _subscriptions = new List<ManagedMqttSubscription>();
public ManagedMqttSubscriptionsManager(IMqttClient mqttClient, IMqttNetLogger logger)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource<ManagedMqttSubscriptionsManager>();
}
public AsyncEvent<SubscribeProcessedEventArgs> SubscribeProcessedEvent { get; } = new AsyncEvent<SubscribeProcessedEventArgs>();
public AsyncEvent<UnsubscribeProcessedEventArgs> UnsubscribeProcessedEvent { get; } = new AsyncEvent<UnsubscribeProcessedEventArgs>();
public void ResubscribeAll()
{
lock (_subscriptions)
{
foreach (var subscription in _subscriptions)
{
subscription.IsPending = true;
}
}
}
public void Subscribe(MqttClientSubscribeOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.TopicFilters.Count > 1)
{
throw new NotSupportedException("The managed client only supports subscribing to one topic at the same time.");
}
var topic = options.TopicFilters.First().Topic;
lock (_subscriptions)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topic);
_subscriptions.RemoveAll(s => s.Topic.Equals(topic));
_subscriptions.Add(new ManagedMqttSubscription(options));
}
}
public async Task Synchronize(CancellationToken cancellationToken)
{
// TODO: Remember real changes.
List<ManagedMqttSubscription> pendingSubscriptions;
List<ManagedMqttSubscription> obsoleteSubscriptions;
lock (_subscriptions)
{
pendingSubscriptions = _subscriptions.Where(s => s.IsPending).ToList();
obsoleteSubscriptions = _subscriptions.Where(s => s.IsObsolete).ToList();
}
if (pendingSubscriptions.Count == 0 && obsoleteSubscriptions.Count == 0)
{
_logger.Verbose("No subscriptions to synchronize");
return;
}
foreach (var subscription in pendingSubscriptions)
{
var subscribeResult = await _mqttClient.SubscribeAsync(subscription.SubscribeOptions, cancellationToken).ConfigureAwait(false);
subscription.IsPending = false;
await FireSubscribeProcessedEvent(subscription, subscribeResult).ConfigureAwait(false);
}
foreach (var subscription in obsoleteSubscriptions)
{
var unsubscribeResult = await _mqttClient.UnsubscribeAsync(subscription.UnsubscribeOptions, cancellationToken).ConfigureAwait(false);
lock (_subscriptions)
{
_subscriptions.Remove(subscription);
}
await FireUnsubscribeProcessedEvent(subscription, unsubscribeResult).ConfigureAwait(false);
}
}
public void Unsubscribe(MqttClientUnsubscribeOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
lock (_subscriptions)
{
var topic = options.TopicFilters.First();
var existingSubscription = _subscriptions.FirstOrDefault(s => s.Topic.Equals(topic));
if (existingSubscription != null)
{
existingSubscription.MarkAsUnsubscribed(options);
}
}
}
async Task FireSubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientSubscribeResult subscribeResult)
{
_logger.Info("Subscribed topic '{0}'.", subscription.Topic);
if (SubscribeProcessedEvent.HasHandlers)
{
var resultCode = subscribeResult?.Items.First().ResultCode ?? MqttClientSubscribeResultCode.UnspecifiedError;
var eventArgs = new SubscribeProcessedEventArgs(subscription.SubscribeOptions, resultCode, subscribeResult?.UserProperties);
await SubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
async Task FireUnsubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientUnsubscribeResult unsubscribeResult)
{
_logger.Info("Unsubscribed topic '{0}'.", subscription.Topic);
if (UnsubscribeProcessedEvent.HasHandlers)
{
var resultCode = unsubscribeResult?.Items.First().ResultCode ?? MqttClientUnsubscribeResultCode.UnspecifiedError;
var eventArgs = new UnsubscribeProcessedEventArgs(subscription.UnsubscribeOptions, resultCode, unsubscribeResult?.UserProperties);
await UnsubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
}
@@ -0,0 +1,103 @@
// 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.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class MqttConnectionManager
{
readonly MqttNetSourceLogger _logger;
readonly IMqttClient _mqttClient;
bool _wasConnected;
public MqttConnectionManager(IMqttClient mqttClient, IMqttNetLogger logger)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource<MqttConnectionManager>();
_mqttClient.DisconnectedAsync += OnClientDisconnected;
}
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
public AsyncEvent<ConnectingFailedEventArgs> ConnectingFailedEvent { get; } = new AsyncEvent<ConnectingFailedEventArgs>();
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
public async Task<MaintainConnectionResult> MaintainConnection(ManagedMqttClientOptions options, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (_wasConnected)
{
try
{
// The only valid way for testing the connection is to send a PING. So we start with this
// in order to check if reconnecting etc. is require or not.
await _mqttClient.PingAsync(cancellationToken).ConfigureAwait(false);
// The client is properly connection. No further action required.
return MaintainConnectionResult.NoChange;
}
catch (Exception exception)
{
_logger.Error(exception, "Error while checking connection.");
}
}
MqttClientConnectResult connectResult = null;
try
{
// The PING was not successful so that there is no working connection. So we connect.
connectResult = await _mqttClient.ConnectAsync(options.ClientOptions, cancellationToken).ConfigureAwait(false);
_wasConnected = true;
_logger.Info("Connected.");
if (ConnectedEvent.HasHandlers)
{
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
await ConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
}
return MaintainConnectionResult.ConnectedEstablished;
}
catch (Exception exception)
{
if (!(exception is OperationCanceledException))
{
_logger.Error(exception, "Error while connecting.");
}
if (ConnectingFailedEvent.HasHandlers)
{
var eventArgs = new ConnectingFailedEventArgs(connectResult, exception);
await ConnectingFailedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
_wasConnected = false;
return MaintainConnectionResult.NotConnected;
}
Task OnClientDisconnected(MqttClientDisconnectedEventArgs arg)
{
_wasConnected = false;
return DisconnectedEvent?.InvokeAsync(arg);
}
}
}
@@ -0,0 +1,309 @@
// 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.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class NewManagedMqttClient : Disposable, IManagedMqttClient
{
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
readonly ApplicationMessagesManager _applicationMessagesManager;
readonly MqttConnectionManager _connectionManager;
readonly AsyncEvent<EventArgs> _isConnectedChangedEvent = new AsyncEvent<EventArgs>();
readonly MqttNetSourceLogger _logger;
readonly ManagedMqttSubscriptionsManager _subscriptionsManager;
readonly ManualResetEventSlim _workSignal = new ManualResetEventSlim();
CancellationTokenSource _workerCancellationToken;
Task _workerTask;
public NewManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
InternalClient.DisconnectedAsync += OnInternalClientDisconnected;
_logger = logger.WithSource(nameof(NewManagedMqttClient));
_connectionManager = new MqttConnectionManager(mqttClient, logger);
_subscriptionsManager = new ManagedMqttSubscriptionsManager(mqttClient, logger);
_applicationMessagesManager = new ApplicationMessagesManager(mqttClient, logger);
// TODO: Start timer for periodical connection checking.
}
public event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync
{
add => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.AddHandler(value);
remove => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.RemoveHandler(value);
}
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
{
add => _applicationMessageProcessedEvent.AddHandler(value);
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => InternalClient.ApplicationMessageReceivedAsync += value;
remove => InternalClient.ApplicationMessageReceivedAsync += value;
}
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync
{
add => _applicationMessagesManager.ApplicationMessageDroppedEvent.AddHandler(value);
remove => _applicationMessagesManager.ApplicationMessageDroppedEvent.RemoveHandler(value);
}
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{
add => _connectionManager.ConnectedEvent.AddHandler(value);
remove => _connectionManager.ConnectedEvent.RemoveHandler(value);
}
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
{
add => _connectionManager.ConnectingFailedEvent.AddHandler(value);
remove => _connectionManager.ConnectingFailedEvent.RemoveHandler(value);
}
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
{
add => _connectionManager.DisconnectedEvent.AddHandler(value);
remove => _connectionManager.DisconnectedEvent.RemoveHandler(value);
}
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync
{
add => _subscriptionsManager.SubscribeProcessedEvent.AddHandler(value);
remove => _subscriptionsManager.SubscribeProcessedEvent.RemoveHandler(value);
}
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync
{
add => _subscriptionsManager.UnsubscribeProcessedEvent.AddHandler(value);
remove => _subscriptionsManager.UnsubscribeProcessedEvent.RemoveHandler(value);
}
public IMqttClient InternalClient { get; }
public bool IsConnected { get; private set; }
public bool IsStarted { get; private set; }
public ManagedMqttClientOptions Options { get; private set; }
public int EnqueuedApplicationMessagesCount => _applicationMessagesManager.EnqueuedApplicationMessagesCount;
public Task EnqueueAsync(MqttApplicationMessage applicationMessage)
{
return EnqueueAsync(new ManagedMqttApplicationMessage(Guid.NewGuid().ToString(), applicationMessage));
}
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
{
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
await _applicationMessagesManager.Enqueue(applicationMessage).ConfigureAwait(false);
_workSignal.Set();
}
public Task PingAsync(CancellationToken cancellationToken = default)
{
return InternalClient.PingAsync(cancellationToken);
}
public Task StartAsync(ManagedMqttClientOptions options)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
_workerCancellationToken = new CancellationTokenSource();
_workerTask = Task.Factory.StartNew(
() => WorkerLoop(_workerCancellationToken.Token),
_workerCancellationToken.Token,
TaskCreationOptions.PreferFairness,
TaskScheduler.Default);
_workSignal.Set();
return CompletedTask.Instance;
}
public async Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// TODO: Send proper Disconnect signal.
try
{
await InternalClient.DisconnectAsync(options, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while disconnecting.");
}
_workerCancellationToken?.Cancel(false);
_workerCancellationToken?.Dispose();
_workerCancellationToken = null;
if (_workerTask != null)
{
}
}
public Task SubscribeAsync(MqttClientSubscribeOptions options)
{
ThrowIfDisposed();
_subscriptionsManager.Subscribe(options);
_workSignal.Set();
return CompletedTask.Instance;
}
public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options)
{
ThrowIfDisposed();
_subscriptionsManager.Unsubscribe(options);
_workSignal.Set();
return CompletedTask.Instance;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
}
}
Task OnInternalClientDisconnected(MqttClientDisconnectedEventArgs eventArgs)
{
_workSignal.Set();
return CompletedTask.Instance;
}
async Task UpdateConnectionStatus(MaintainConnectionResult maintainConnectionResult)
{
var isConnected = maintainConnectionResult != MaintainConnectionResult.NotConnected;
if (IsConnected.Equals(isConnected))
{
return;
}
IsConnected = isConnected;
if (_isConnectedChangedEvent.HasHandlers)
{
await _isConnectedChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
}
}
async Task WorkerLoop(CancellationToken cancellationToken)
{
try
{
IsStarted = true;
while (!cancellationToken.IsCancellationRequested)
{
_workSignal.Wait(cancellationToken);
_workSignal.Reset();
var maintainConnectionResult = await _connectionManager.MaintainConnection(Options, cancellationToken).ConfigureAwait(false);
await UpdateConnectionStatus(maintainConnectionResult).ConfigureAwait(false);
if (maintainConnectionResult == MaintainConnectionResult.ConnectedEstablished && Options.ClientOptions.CleanSession)
{
// Since the session is clean the server will now know any subscription so that
// we have to subscribe to all of them again.
_subscriptionsManager.ResubscribeAll();
}
if (IsConnected)
{
await _subscriptionsManager.Synchronize(cancellationToken).ConfigureAwait(false);
}
if (IsConnected)
{
// TODO: Move into pending messages manager + locking.
while (_applicationMessagesManager.TryPeek(out var nextPendingMessage))
{
Exception processingException = null;
try
{
_logger.Verbose("Publishing managed application message {0}.", nextPendingMessage.Id);
await InternalClient.PublishAsync(nextPendingMessage.ApplicationMessage, cancellationToken).ConfigureAwait(false);
// The message was delivered successfully. So we can get rid of it.
_applicationMessagesManager.Dequeue();
}
catch (Exception exception)
{
processingException = exception;
}
finally
{
if (_applicationMessageProcessedEvent.HasHandlers)
{
var eventArgs = new ApplicationMessageProcessedEventArgs(nextPendingMessage, processingException);
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
}
}
}
catch (OperationCanceledException)
{
}
#if !NETSTANDARD1_3
catch (ThreadAbortException)
{
}
#endif
catch (Exception exception)
{
_logger.Error(exception, "Error while maintaining connection.");
}
finally
{
IsStarted = false;
await UpdateConnectionStatus(MaintainConnectionResult.NotConnected).ConfigureAwait(false);
}
// TODO: Disconnected!
}
}
}
@@ -10,6 +10,15 @@ namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClientOptions
{
public ManagedMqttClientOptions()
{
}
public ManagedMqttClientOptions(MqttClientOptions clientOptions)
{
ClientOptions = clientOptions;
}
public MqttClientOptions ClientOptions { get; set; }
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
@@ -24,7 +33,7 @@ namespace MQTTnet.Extensions.ManagedClient
/// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
/// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
}
@@ -8,7 +8,7 @@ using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedMqttClientOptionsBuilder
public sealed class ManagedMqttClientOptionsBuilder
{
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
MqttClientOptionsBuilder _clientOptionsBuilder;
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -8,8 +7,8 @@ namespace MQTTnet.Extensions.Rpc
{
public interface IMqttRpcClient : IDisposable
{
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default);
}
}
@@ -2,7 +2,7 @@
<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' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
@@ -4,8 +4,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
@@ -43,13 +41,13 @@ namespace MQTTnet.Extensions.Rpc
_waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
{
try
{
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
@@ -63,14 +61,14 @@ namespace MQTTnet.Extensions.Rpc
}
}
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
{
if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
@@ -121,6 +119,7 @@ namespace MQTTnet.Extensions.Rpc
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -132,8 +131,7 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer);
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -12,13 +11,13 @@ namespace MQTTnet.Extensions.Rpc
{
public static class MqttRpcClientExtensions
{
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel);
}
}
}
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Client;
using MQTTnet.Protocol;
@@ -11,11 +10,9 @@ namespace MQTTnet.Extensions.Rpc
{
public sealed class TopicGenerationContext
{
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
@@ -23,8 +20,6 @@ namespace MQTTnet.Extensions.Rpc
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
@@ -2,7 +2,7 @@
<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' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -47,13 +47,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{
if (_webSocketOptions.TlsOptions?.UseTls == true)
if (_webSocketOptions.TlsOptions?.UseTls == false)
{
uri = "wss://" + uri;
uri = "ws://" + uri;
}
else
{
uri = "ws://" + uri;
uri = "wss://" + uri;
}
}
@@ -92,7 +92,18 @@ namespace MQTTnet.Extensions.WebSocket4Net
var webSocketVersion = WebSocketVersion.None;
var receiveBufferSize = 0;
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
var certificates = new X509CertificateCollection();
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)
{
@@ -27,10 +27,8 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter(
new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
logger)
{
PacketInspector = packetInspector
};
packetInspector,
logger);
}
case MqttClientWebSocketOptions webSocketOptions:
@@ -38,10 +36,8 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
logger)
{
PacketInspector = packetInspector
};
packetInspector,
logger);
}
default:
+2 -12
View File
@@ -4,7 +4,6 @@
using MQTTnet.Client;
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
@@ -28,24 +27,15 @@ namespace MQTTnet.TestApp
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
Server = "127.0.0.1"
}
};
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($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {payloadText}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine();
@@ -10,7 +10,6 @@
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
+1 -2
View File
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Internal;
@@ -29,7 +28,7 @@ namespace MQTTnet.TestApp
Credentials = new RandomPassword(),
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("broker.hivemq.com", 0)
Server = "broker.hivemq.com"
}
},
+11 -14
View File
@@ -5,13 +5,13 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.TestApp
{
@@ -25,7 +25,7 @@ namespace MQTTnet.TestApp
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
Server = "127.0.0.1"
},
CleanSession = true
};
@@ -71,7 +71,7 @@ namespace MQTTnet.TestApp
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
Server = "127.0.0.1"
}
};
@@ -114,10 +114,7 @@ namespace MQTTnet.TestApp
{
var options = new MqttClientOptions
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("localhost", 0)
},
ChannelOptions = new MqttClientTcpOptions { Server = "localhost" },
ClientId = "Client1",
CleanSession = true
};
@@ -199,7 +196,7 @@ namespace MQTTnet.TestApp
return new MqttApplicationMessage
{
Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
Payload = Encoding.UTF8.GetBytes(Payload),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
};
}
@@ -221,7 +218,7 @@ namespace MQTTnet.TestApp
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
Server = "127.0.0.1"
},
CleanSession = true
};
@@ -232,7 +229,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage
{
Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
Payload = Encoding.UTF8.GetBytes("Hello World"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
};
@@ -272,7 +269,7 @@ namespace MQTTnet.TestApp
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
Server = "127.0.0.1"
},
CleanSession = true
};
@@ -283,7 +280,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage
{
Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
Payload = Encoding.UTF8.GetBytes("Hello World"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
};
@@ -323,7 +320,7 @@ namespace MQTTnet.TestApp
{
ChannelOptions = new MqttClientTcpOptions
{
RemoteEndpoint = new DnsEndPoint("127.0.0.1", 0)
Server = "127.0.0.1"
},
CleanSession = true
};
@@ -334,7 +331,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage
{
Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
Payload = Encoding.UTF8.GetBytes("Hello World"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
};
+52 -65
View File
@@ -20,155 +20,143 @@ namespace MQTTnet.TestApp
{
#if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientTlsOptions
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
SslProtocol = SslProtocols.Tls13,
// Don't use this in production code. This handler simply allows any invalid certificate to work.
AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
CertificateValidationHandler = w => true
};
#endif
// Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientTlsOptions
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
SslProtocol = SslProtocols.Tls12,
// Don't use this in production code. This handler simply allows any invalid certificate to work.
AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
CertificateValidationHandler = w => true
};
// mqtt.eclipseprojects.io
await ExecuteTestsAsync(
await ExecuteTestAsync(
"mqtt.eclipseprojects.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER
await ExecuteTestsAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
await ExecuteTestsAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
true);
#endif
// test.mosquitto.org
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org TCP - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestsAsync("test.mosquitto.org TCP TLS13",
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12 - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTlsOptions(unsafeTls12)
.WithTls(unsafeTls12)
.Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestsAsync(
await ExecuteTestAsync(
"test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestsAsync(
"test.mosquitto.org WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
true);
// await ExecuteTestAsync(
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
// true);
// broker.emqx.io
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.emqx.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestsAsync("broker.emqx.io TCP TLS13",
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.emqx.io WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.emqx.io WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
true);
// broker.hivemq.com
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.hivemq.com TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestsAsync(
await ExecuteTestAsync(
"broker.hivemq.com WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
// mqtt.swifitch.cz: Does not seem to operate any more
// cloudmqtt.com: Cannot test because it does not offer a free plan any more.
Write("Finished.", ConsoleColor.White);
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)
{
try
@@ -209,10 +197,9 @@ namespace MQTTnet.TestApp
Write("[OK]\n", ConsoleColor.Green);
}
catch (Exception exception)
catch (Exception e)
{
Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red);
Write(exception + Environment.NewLine, ConsoleColor.Red);
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red);
}
}
+4 -11
View File
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
@@ -101,7 +100,7 @@ namespace MQTTnet.TestApp
{
// Replace the payload with the timestamp. But also extending a JSON
// based payload with the timestamp is a suitable use case.
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
}
if (e.ApplicationMessage.Topic == "not_allowed_topic")
@@ -144,16 +143,10 @@ namespace MQTTnet.TestApp
mqttServer.InterceptingPublishAsync += 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);
}
MqttNetConsoleLogger.PrintToConsole(
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
ConsoleColor.Magenta);
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
return CompletedTask.Instance;
};
@@ -13,7 +13,6 @@ using MQTTnet.Exceptions;
using MQTTnet.LowLevelClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{
@@ -9,7 +9,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Formatter;
using MQTTnet.Internal;
@@ -20,18 +19,17 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
public sealed class ManagedMqttClient_Connection_Tests : BaseTestClass
{
[TestMethod]
public async Task Connect_To_Invalid_Server()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
await testEnvironment.StartServer();
await LongTestDelay();
var mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
ConnectingFailedEventArgs connectingFailedEventArgs = null;
managedClient.ConnectingFailedAsync += args =>
@@ -57,112 +55,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
}
}
[TestMethod]
public async Task Drop_New_Messages_On_Full_Queue()
{
var factory = new MqttFactory();
var managedClient = factory.CreateManagedMqttClient();
try
{
var clientOptions = new ManagedMqttClientOptionsBuilder().WithMaxPendingMessages(5)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
clientOptions.WithClientOptions(o => o.WithTcpServer("localhost"));
await managedClient.StartAsync(clientOptions.Build());
await managedClient.EnqueueAsync("1");
await managedClient.EnqueueAsync("2");
await managedClient.EnqueueAsync("3");
await managedClient.EnqueueAsync("4");
await managedClient.EnqueueAsync("5");
await managedClient.EnqueueAsync("6");
await managedClient.EnqueueAsync("7");
await managedClient.EnqueueAsync("8");
Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount);
}
finally
{
await managedClient.StopAsync();
}
}
[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]
public async Task ManagedClients_Will_Message_Send()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var receivedMessagesCount = 0;
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithWillTopic("My/last/will")
.WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
.Build();
var dyingClient = testEnvironment.CreateClient();
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
await dyingManagedClient.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;
};
await receivingClient.SubscribeAsync("My/last/will");
// Disposing the client will not sent a DISCONNECT packet so that connection is terminated
// which will lead to the will publish.
dyingManagedClient.Dispose();
// Wait for arrival of the will message at the receiver.
await Task.Delay(5000);
Assert.AreEqual(1, receivedMessagesCount);
}
}
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
@@ -231,18 +123,18 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
var server = await testEnvironment.StartServer();
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.CreateManagedClient();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
await connected;
await managedClient.StopAsync();
await Task.Delay(500);
await LongTestDelay();
Assert.AreEqual(0, (await server.GetClientsAsync()).Count);
}
@@ -258,7 +150,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await testEnvironment.StartServer();
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.CreateManagedClient();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage();
@@ -298,6 +190,8 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var unmanagedClient = testEnvironment.CreateClient();
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
@@ -314,7 +208,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.SubscribeAsync("unsubscribedThenSubscribed");
//wait a bit for the subscriptions to become established before the messages are published
await Task.Delay(500);
await LongTestDelay();
var sendingClient = await testEnvironment.ConnectClient();
@@ -344,7 +238,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await connected;
// wait a bit so that the managed client can reestablish the subscriptions
await Task.Delay(500);
await LongTestDelay();
received = SetupReceivingOfMessages(managedClient, 2);
@@ -360,7 +254,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer().ConfigureAwait(false);
var server = await testEnvironment.StartServer().ConfigureAwait(false);
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
await sendingClient.PublishStringAsync("topic", "A", retain: true);
@@ -370,10 +264,10 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await sendingClient.DisconnectAsync();
// Prepare retained messages.
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("topic").WithPayload("payload").WithRetainFlag().Build());
// Now use the managed client and check if subscriptions get cleared properly.
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
@@ -385,7 +279,10 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
var managedClientOptions = testEnvironment.CreateDefaultManagedMqttClientOptions();
managedClientOptions.ClientOptions.CleanSession = false;
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count);
@@ -393,7 +290,8 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.StopAsync();
await LongTestDelay();
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
// After reconnect and then some delay, the retained message must not be received,
@@ -415,13 +313,14 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
// Use a long connection check interval to verify that the subscriptions
// do not depend on the connection check interval anymore
var connectionCheckInterval = TimeSpan.FromSeconds(10);
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var subscribeTime = DateTime.UtcNow;
@@ -437,109 +336,6 @@ 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
// 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)
@@ -548,7 +344,9 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
using (var testEnvironment = CreateTestEnvironment())
{
var managedClient = await CreateManagedClientAsync(testEnvironment);
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
var sendingClient = await testEnvironment.ConnectClient();
@@ -557,7 +355,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
//wait a bit for the subscription to become established
await Task.Delay(500);
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var messages = await SetupReceivingOfMessages(managedClient, 1);
@@ -574,14 +372,52 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
}
}
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
[TestMethod]
public async Task Will_Message_Send()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
// Prepare the receiving client.
var receivedMessagesCount = 0;
var receivingClient = await testEnvironment.ConnectClient();
receivingClient.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance;
};
await receivingClient.SubscribeAsync("My/last/will");
// Prepare the managed client.
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithWillTopic("My/last/will").Build();
var dyingClient = testEnvironment.CreateClient();
var dyingManagedClient = testEnvironment.CreateManagedMqttClient(dyingClient);
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
// Wait until the managed client is fully set up and running.
await LongTestDelay();
// Disposing the client will not sent a DISCONNECT packet so that connection is terminated
// which will lead to the will publish.
dyingManagedClient.Dispose();
// Wait for arrival of the will message at the receiver.
await LongTestDelay();
Assert.AreEqual(1, receivedMessagesCount);
}
}
static async Task<IManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment,
IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null,
string host = "localhost")
{
await testEnvironment.StartServer();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer(host, testEnvironment.ServerPort);
var managedOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build();
@@ -591,7 +427,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
// at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.CreateManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient());
var connected = GetConnectedTask(managedClient);
@@ -602,35 +438,20 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
return managedClient;
}
/// <summary>
/// Returns a task that will finish when the
/// <paramref
/// name="managedClient" />
/// has connected
/// </summary>
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
static Task GetConnectedTask(IManagedMqttClient managedClient)
{
var connected = new TaskCompletionSource<bool>();
var promise = new TaskCompletionSource<bool>();
managedClient.ConnectedAsync += e =>
{
connected.TrySetResult(true);
promise.TrySetResult(true);
return CompletedTask.Instance;
};
return connected.Task;
return promise.Task;
}
/// <summary>
/// Returns a task that will return the messages received on
/// <paramref
/// name="managedClient" />
/// when
/// <paramref
/// name="expectedNumberOfMessages" />
/// have been received
/// </summary>
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
static Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(IManagedMqttClient managedClient, int expectedNumberOfMessages)
{
var receivedMessages = new List<MqttApplicationMessage>();
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -653,7 +474,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
{
IList<ManagedMqttApplicationMessage> _messages;
IList<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>();
public int GetMessageCount()
{
@@ -662,11 +483,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
{
if (_messages == null)
{
_messages = new List<ManagedMqttApplicationMessage>();
}
return Task.FromResult(_messages);
}
@@ -0,0 +1,178 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Internal;
namespace MQTTnet.Tests.ManagedMqttClient
{
[TestClass]
public sealed class ManagedMqttClient_Event_Tests : BaseTestClass
{
[TestMethod]
public async Task Connecting_Should_Fire_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
var eventFired = false;
managedClient.ConnectedAsync += e =>
{
eventFired = true;
return CompletedTask.Instance;
};
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
await LongTestDelay();
Assert.IsTrue(eventFired);
}
}
[TestMethod]
public async Task Connecting_To_Invalid_Server_Should_Fire_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var managedClient = testEnvironment.CreateManagedMqttClient();
ConnectingFailedEventArgs connectingFailedEventArgs = null;
managedClient.ConnectingFailedAsync += args =>
{
connectingFailedEventArgs = args;
return CompletedTask.Instance;
};
await managedClient.StartAsync(
new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(2)).WithTcpServer("wrong_server", 1234).Build()
});
SpinWait.SpinUntil(() => connectingFailedEventArgs != null, 10000);
// The wrong server must be reported in general.
Assert.IsNotNull(connectingFailedEventArgs);
Assert.IsNotNull(connectingFailedEventArgs.Exception);
Assert.IsNull(connectingFailedEventArgs.ConnectResult);
}
}
[TestMethod]
public async Task Disconnect_Should_Fire_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
var eventFired = false;
managedClient.DisconnectedAsync += e =>
{
eventFired = true;
return CompletedTask.Instance;
};
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
await LongTestDelay();
await server.StopAsync();
await LongTestDelay();
Assert.IsTrue(eventFired);
}
}
[TestMethod]
public async Task Enqueueing_Should_Fire_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
var eventFired = false;
managedClient.ApplicationMessageEnqueueingAsync += e =>
{
eventFired = true;
return CompletedTask.Instance;
};
await managedClient.EnqueueAsync("A");
Assert.IsTrue(eventFired);
}
}
[TestMethod]
public async Task Subscription_Should_Fire_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
await managedClient.SubscribeAsync("A");
var eventFired = false;
managedClient.SubscribeProcessedAsync += e =>
{
eventFired = e.Options.TopicFilters[0].Topic == "A";
return CompletedTask.Instance;
};
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
await LongTestDelay();
Assert.IsTrue(eventFired);
}
}
[TestMethod]
public async Task Unsubscription_Should_Fire_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
await managedClient.SubscribeAsync("A");
var eventFired = false;
managedClient.UnsubscribeProcessedAsync += e =>
{
eventFired = e.Options.TopicFilters[0] == "A";
return CompletedTask.Instance;
};
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
await LongTestDelay();
await managedClient.UnsubscribeAsync("A");
await LongTestDelay();
Assert.IsTrue(eventFired);
}
}
}
}
@@ -0,0 +1,135 @@
// 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Server;
namespace MQTTnet.Tests.ManagedMqttClient
{
[TestClass]
public sealed class ManagedMqttClient_Messages_Tests : BaseTestClass
{
[TestMethod]
public async Task Drop_New_Messages_On_Full_Queue()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedMqttClient();
var clientOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(o => o.WithTcpServer("localhost"))
.WithMaxPendingMessages(5)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
await managedClient.StartAsync(clientOptions.Build());
await managedClient.EnqueueAsync("1");
await managedClient.EnqueueAsync("2");
await managedClient.EnqueueAsync("3");
await managedClient.EnqueueAsync("4");
await managedClient.EnqueueAsync("5");
await managedClient.EnqueueAsync("6");
await managedClient.EnqueueAsync("7");
await managedClient.EnqueueAsync("8");
Assert.AreEqual(5, managedClient.EnqueuedApplicationMessagesCount);
}
}
[TestMethod]
public async Task Drop_Old_Messages_On_Full_Queue()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedMqttClient();
var clientOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(o => o.WithTcpServer("localhost"))
.WithMaxPendingMessages(5)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage);
await managedClient.StartAsync(clientOptions.Build());
await managedClient.EnqueueAsync("1");
await managedClient.EnqueueAsync("2");
await managedClient.EnqueueAsync("3");
await managedClient.EnqueueAsync("4");
await managedClient.EnqueueAsync("5");
await managedClient.EnqueueAsync("6");
await managedClient.EnqueueAsync("7");
await managedClient.EnqueueAsync("8");
Assert.AreEqual(5, managedClient.EnqueuedApplicationMessagesCount);
}
}
[TestMethod]
public async Task Preserve_Order_Of_Subscriptions()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
// Prepare the retained messages.
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("A").WithPayload("P").WithRetainFlag().Build());
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("B").WithPayload("P").WithRetainFlag().Build());
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("C").WithPayload("P").WithRetainFlag().Build());
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("3").WithPayload("P").WithRetainFlag().Build());
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("2").WithPayload("P").WithRetainFlag().Build());
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("1").WithPayload("P").WithRetainFlag().Build());
// Subscribe to the retained messages in a defined order.
var managedClient = testEnvironment.CreateManagedMqttClient();
var applicationMessages = new List<MqttApplicationMessage>();
managedClient.ApplicationMessageReceivedAsync += e =>
{
applicationMessages.Add(e.ApplicationMessage);
return CompletedTask.Instance;
};
await managedClient.SubscribeAsync("B");
await managedClient.SubscribeAsync("C");
await managedClient.SubscribeAsync("A");
await managedClient.SubscribeAsync("2");
await managedClient.SubscribeAsync("1");
await managedClient.SubscribeAsync("3");
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions()).ConfigureAwait(false);
await LongTestDelay();
var order = string.Join(string.Empty, applicationMessages.Select(a => a.Topic));
Assert.AreEqual("BCA213", order);
}
}
[TestMethod]
public async Task Publish()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.TrackServerReceivedApplicationMessages = true;
await testEnvironment.StartServer();
var managedClient = await testEnvironment.StartManagedClient();
await managedClient.EnqueueAsync(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("TEST").Build());
await LongTestDelay();
Assert.AreEqual(1, testEnvironment.ServerReceivedApplicationMessages.Count);
}
}
}
}
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -11,7 +10,6 @@ using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -101,8 +99,6 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
@@ -129,8 +125,6 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
}
@@ -156,9 +150,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count);
@@ -166,89 +158,5 @@ namespace MQTTnet.Tests.Clients.MqttClient
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("127.0.0.1").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 (MqttCommunicationException exception)
{
if (exception.Message == "The client is not connected.")
{
return;
}
}
Assert.Fail();
}
}
}
@@ -17,7 +17,6 @@ using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
@@ -297,7 +296,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
Assert.AreEqual(null, receivedMessage.Payload);
}
}
@@ -508,7 +507,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
client2.ApplicationMessageReceivedAsync += e =>
{
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
return CompletedTask.Instance;
};
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
var packets = new List<string>();
mqttClient.InspectPacketAsync += eventArgs =>
mqttClient.InspectPackage += eventArgs =>
{
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return CompletedTask.Instance;
+98
View File
@@ -0,0 +1,98 @@
// 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}");
}
}
}
}
+1 -57
View File
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -46,34 +45,6 @@ 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]
public Task Execute_Success_With_QoS_0()
{
@@ -164,7 +135,7 @@ namespace MQTTnet.Tests.Extensions
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
}
@@ -251,32 +222,5 @@ 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())
{
var options = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("ws://a.b/mqtt")).WithTimeout(TimeSpan.FromSeconds(2)).Build();
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
await client.ConnectAsync(options).ConfigureAwait(false);
}
}
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
Payload = Encoding.ASCII.GetBytes("HELLO"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C"
};
@@ -318,9 +318,10 @@ namespace MQTTnet.Tests.Formatter
var publishPacket = new MqttPublishPacket
{
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
PayloadSegment = new ArraySegment<byte>(payload)
Payload = payload
};
var serializationHelper = new MqttPacketSerializationHelper();
var buffer = serializationHelper.Encode(publishPacket);
@@ -328,17 +329,17 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNotNull(publishPacketCopy);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload);
// Now modify the payload and test again.
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
buffer = serializationHelper.Encode(publishPacket);
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
Assert.IsNotNull(publishPacketCopy2);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
}
[TestMethod]
@@ -462,7 +463,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
Payload = Encoding.ASCII.GetBytes("HELLO"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C"
};
@@ -566,6 +567,7 @@ namespace MQTTnet.Tests.Formatter
using (var adapter = new MqttChannelAdapter(
channel,
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
null,
new MqttNetEventLogger()))
{
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -581,12 +583,19 @@ namespace MQTTnet.Tests.Formatter
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
{
var channel = new MemoryMqttChannel(buffer);
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
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
{
var writer = bufferWriter ?? WriterFactory();
@@ -595,7 +604,7 @@ namespace MQTTnet.Tests.Formatter
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
{
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
@@ -2,9 +2,7 @@
// 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;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
@@ -78,7 +76,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
}
[TestMethod]
[TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310()
{
var connAckPacket = new MqttConnAckPacket
@@ -298,7 +296,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
Payload = Encoding.ASCII.GetBytes("Payload"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic",
ResponseTopic = "/Response",
@@ -322,7 +320,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
@@ -2,9 +2,7 @@
// 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;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter;
@@ -259,7 +257,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
Payload = Encoding.ASCII.GetBytes("Payload"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic",
ResponseTopic = "/Response",
@@ -283,7 +281,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter
@@ -48,32 +47,23 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(1234U, 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]
public void Write_And_Read_Multiple_Times()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinary(new byte[1234]);
writer.WriteBinaryData(new byte[1234]);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinary(new byte[48]);
writer.WriteBinaryData(new byte[48]);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(new byte[3]);
writer.WriteBinaryData(new byte[3]);
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
+1 -2
View File
@@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net48</TargetFrameworks>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
+1 -1
View File
@@ -273,7 +273,7 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
CollectionAssert.AreEqual(applicationMessage.PayloadSegment.ToArray(), receivedMessage.PayloadSegment.ToArray());
CollectionAssert.AreEqual(applicationMessage.Payload, receivedMessage.Payload);
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
}
}
@@ -10,7 +10,6 @@ using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.MQTTv5
{
@@ -51,7 +51,10 @@ namespace MQTTnet.Tests.Mockups
public void AssertReceivedCountEquals(int expectedCount)
{
Assert.AreEqual(expectedCount, Count);
lock (_receivedEventArgs)
{
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
}
}
public string GeneratePayloadSequence()
+123 -108
View File
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#region
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -19,14 +21,18 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Protocol;
using MQTTnet.Server;
#endregion
namespace MQTTnet.Tests.Mockups
{
public sealed class TestEnvironment : IDisposable
{
readonly List<string> _clientErrors = new List<string>();
readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>();
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly List<IManagedMqttClient> _managedClients = new List<IManagedMqttClient>();
readonly MqttProtocolVersion _protocolVersion;
readonly List<string> _serverErrors = new List<string>();
@@ -39,8 +45,6 @@ namespace MQTTnet.Tests.Mockups
_protocolVersion = protocolVersion;
TestContext = testContext;
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
ServerLogger.LogMessagePublished += (s, e) =>
{
if (Debugger.IsAttached)
@@ -66,12 +70,9 @@ namespace MQTTnet.Tests.Mockups
if (e.LogMessage.Level == MqttNetLogLevel.Error)
{
if (!IgnoreClientLogErrors)
lock (_clientErrors)
{
lock (_clientErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
}
_clientErrors.Add(e.LogMessage.ToString());
}
}
};
@@ -93,8 +94,12 @@ namespace MQTTnet.Tests.Mockups
public int ServerPort { get; set; }
public List<MqttApplicationMessage> ServerReceivedApplicationMessages { get; } = new List<MqttApplicationMessage>();
public TestContext TestContext { get; }
public bool TrackServerReceivedApplicationMessages { get; set; }
public Task<IMqttClient> ConnectClient()
{
return ConnectClient(Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion));
@@ -216,31 +221,35 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient()
{
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e =>
{
if (TestContext != null)
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
lock (_clients)
{
_clients.Add(client);
}
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
return client;
var client = Factory.CreateMqttClient(logger);
_clients.Add(client);
client.ConnectingAsync += e =>
{
if (TestContext != null)
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
return client;
}
}
public ManagedMqttClientOptions CreateDefaultManagedMqttClientOptions()
{
return Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(Factory.CreateClientOptionsBuilder().WithTcpServer("127.0.0.1", ServerPort).Build()).Build();
}
public MqttClientOptions CreateDefaultClientOptions()
@@ -250,22 +259,33 @@ namespace MQTTnet.Tests.Mockups
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{
return Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(_protocolVersion)
.WithTcpServer("127.0.0.1", ServerPort)
.WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
}
public ILowLevelMqttClient CreateLowLevelClient()
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
lock (_lowLevelClients)
lock (_clients)
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
}
return client;
return client;
}
}
public IManagedMqttClient CreateManagedClient()
{
return Factory.CreateManagedMqttClient(CreateClient());
}
public IManagedMqttClient CreateManagedMqttClient()
{
return Factory.CreateManagedMqttClient(CreateClient(), ClientLogger);
}
public IManagedMqttClient CreateManagedMqttClient(IMqttClient client)
{
return Factory.CreateManagedMqttClient(client, ClientLogger);
}
public MqttServer CreateServer(MqttServerOptions options)
@@ -279,6 +299,11 @@ namespace MQTTnet.Tests.Mockups
Server = Factory.CreateMqttServer(options, logger);
if (TrackServerReceivedApplicationMessages)
{
Server.InterceptingPublishAsync += ServerOnInterceptingPublishAsync;
}
Server.ValidatingConnectionAsync += e =>
{
if (TestContext != null)
@@ -299,69 +324,65 @@ namespace MQTTnet.Tests.Mockups
public void Dispose()
{
try
foreach (var mqttClient in _clients)
{
lock (_clients)
{
foreach (var mqttClient in _clients)
{
try
{
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already disconnected the client.
}
finally
{
mqttClient?.Dispose();
}
}
_clients.Clear();
}
lock (_lowLevelClients)
{
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
}
_lowLevelClients.Clear();
}
try
{
Server?.StopAsync().GetAwaiter().GetResult();
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already stopped the server.
// This can happen when the test already disconnected the client.
}
finally
{
Server?.Dispose();
mqttClient?.Dispose();
}
}
Server = null;
ThrowIfLogErrors();
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
if (_exceptions.Any())
foreach (var managedMqttClient in _managedClients)
{
try
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
managedMqttClient.StopAsync().Wait();
}
finally
{
managedMqttClient.Dispose();
}
}
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
}
try
{
Server?.StopAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already stopped the server.
}
finally
{
TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException;
Server?.Dispose();
}
ThrowIfLogErrors();
if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
}
}
public async Task<IManagedMqttClient> StartManagedClient()
{
var managedClient = Factory.CreateManagedMqttClient(CreateClient());
await managedClient.StartAsync(CreateDefaultManagedMqttClientOptions());
return managedClient;
}
public Task<MqttServer> StartServer()
@@ -377,7 +398,7 @@ namespace MQTTnet.Tests.Mockups
var options = optionsBuilder.Build();
var server = CreateServer(options);
await server.StartAsync().ConfigureAwait(false);
await server.StartAsync();
// The OS has chosen the port to we have to properly expose it to the tests.
ServerPort = options.DefaultEndpointOptions.Port;
@@ -405,49 +426,43 @@ namespace MQTTnet.Tests.Mockups
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)}).";
Console.WriteLine(message);
throw new Exception(message);
}
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
Console.WriteLine(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)})";
Console.WriteLine(message);
throw new Exception(message);
}
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
}
}
}
public void TrackException(Exception exception)
{
if (exception == null)
{
return;
}
lock (_exceptions)
{
_exceptions.Add(exception);
}
}
void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
Task ServerOnInterceptingPublishAsync(InterceptingPublishEventArgs arg)
{
TrackException(e.Exception);
lock (ServerReceivedApplicationMessages)
{
ServerReceivedApplicationMessages.Add(arg.ApplicationMessage);
}
return CompletedTask.Instance;
}
}
}
@@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Protocol;
@@ -12,7 +11,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttApplicationMessageBuilder_Tests
public class MqttApplicationMessageBuilder_Tests
{
[TestMethod]
public void CreateApplicationMessage_TopicOnly()
@@ -30,7 +29,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("xyz", message.Topic);
Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00");
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
}
[TestMethod]
@@ -42,7 +41,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("123", message.Topic);
Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
}
[TestMethod]
@@ -1,45 +0,0 @@
#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 adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
writer.WriteString("1234567890");
Assert.AreEqual(10 + 2, writer.Length);
writer.WriteBinary(new byte[300]);
writer.WriteBinaryData(new byte[300]);
Assert.AreEqual(300 + 2 + 12, writer.Length);
writer.WriteBinary(new byte[5000]);
writer.WriteBinaryData(new byte[5000]);
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
}
}
+17 -14
View File
@@ -303,7 +303,7 @@ namespace MQTTnet.Tests.Server
var server = await testEnvironment.StartServer();
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("extended"));
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
return CompletedTask.Instance;
};
@@ -314,7 +314,7 @@ namespace MQTTnet.Tests.Server
var isIntercepted = false;
c2.ApplicationMessageReceivedAsync += e =>
{
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()), StringComparison.Ordinal) == 0;
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
return CompletedTask.Instance;
};
@@ -425,7 +425,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "/test/1",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("true")),
Payload = Encoding.UTF8.GetBytes("true"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
})
{
@@ -598,7 +598,8 @@ namespace MQTTnet.Tests.Server
// c
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await LongTestDelay();
await Task.Delay(500);
var flow = string.Join(string.Empty, events);
Assert.AreEqual("c", flow);
@@ -606,8 +607,10 @@ namespace MQTTnet.Tests.Server
// Connect client with same client ID. Should disconnect existing client.
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await LongTestDelay();
await Task.Delay(500);
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdc", flow);
c2.ApplicationMessageReceivedAsync += e =>
@@ -625,7 +628,8 @@ namespace MQTTnet.Tests.Server
// r
await c2.PublishStringAsync("topic");
await LongTestDelay();
await Task.Delay(500);
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcr", flow);
@@ -635,17 +639,16 @@ namespace MQTTnet.Tests.Server
await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected);
await LongTestDelay();
await Task.Delay(500);
// d
Assert.AreEqual(true, c2.IsConnected);
await c2.DisconnectAsync();
await LongTestDelay();
await Task.Delay(500);
await server.StopAsync();
await LongTestDelay();
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcrd", flow);
}
@@ -780,7 +783,7 @@ namespace MQTTnet.Tests.Server
var client1 = await testEnvironment.ConnectClient();
client1.ApplicationMessageReceivedAsync += e =>
{
receivedBody = e.ApplicationMessage.PayloadSegment.ToArray();
receivedBody = e.ApplicationMessage.Payload;
return CompletedTask.Instance;
};
@@ -849,11 +852,11 @@ namespace MQTTnet.Tests.Server
return CompletedTask.Instance;
};
// Stopping the server should disconnect the connection with the client
// which, it turn, will fire the disconnected event at the client.
await Task.Delay(100);
await server.StopAsync();
await LongTestDelay();
await Task.Delay(100);
Assert.AreEqual(1, disconnectCalled);
}
@@ -951,7 +954,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient();
client.InspectPacketAsync += e =>
client.InspectPackage += e =>
{
if (e.Buffer.Length > 0)
{
@@ -1,426 +0,0 @@
#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
@@ -1,25 +0,0 @@
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,17 +221,14 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer();
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
var session = new MqttSession(new MqttConnectPacket
{
ClientId = ""
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
}
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _);
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
}
}
@@ -0,0 +1,58 @@
// 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,10 +5,7 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
@@ -30,118 +27,5 @@ namespace MQTTnet.Tests.Server
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 ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
}
}
+12 -30
View File
@@ -70,28 +70,6 @@ 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]
public async Task Fire_Deleted_Event()
{
@@ -143,7 +121,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
}
}
[TestMethod]
[DataRow(MqttProtocolVersion.V310)]
[DataRow(MqttProtocolVersion.V311)]
@@ -156,12 +134,16 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
var hasReceive = false;
void OnReceive()
{
hasReceive = true;
if (!hasReceive)
{
hasReceive = true;
}
}
// Try to connect 50 clients at the same time.
@@ -170,7 +152,7 @@ namespace MQTTnet.Tests.Server
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay();
Assert.AreEqual(1, connectedClients.Count);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
@@ -182,7 +164,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive);
}
}
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
@@ -283,7 +265,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty));
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
return CompletedTask.Instance;
};
@@ -373,8 +355,8 @@ namespace MQTTnet.Tests.Server
onReceive();
return CompletedTask.Instance;
};
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
}
+2 -25
View File
@@ -62,7 +62,7 @@ namespace MQTTnet.Tests.Server
}
}
}
[TestMethod]
public async Task Deny_Invalid_Topic()
{
@@ -90,29 +90,6 @@ 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]
[ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Subscribing()
@@ -128,7 +105,7 @@ namespace MQTTnet.Tests.Server
await client.SubscribeAsync("#");
}
}
[TestMethod]
public async Task Enqueue_Message_After_Subscription()
{
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
var topics = t.Value;
foreach (var topic in topics)
{
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
bucketDepths.TryGetValue(topicHash, out var currentValue);
++currentValue;
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
{
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
}
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
{
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
}
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
// UInt64 is limited to 8 levels
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString();
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString();
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
var l3 = "#";
var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
{
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
Console.WriteLine();
Console.WriteLine("Topic: " + topic);
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
{
var topicToFind = topicsToFind[countDown];
MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _);
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
if (result.IsSubscribed)
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer();
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
_clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
List<string> topicsToSubscribe;
-202
View File
@@ -1,202 +0,0 @@
#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,14 +2,10 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
@@ -32,28 +28,5 @@ namespace MQTTnet.Tests.Server
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,8 +150,18 @@ namespace MQTTnet.Tests
{
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
ulong topicHash;
ulong topicHashMask;
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)
{
@@ -25,6 +25,8 @@ namespace MQTTnet.Adapter
long BytesReceived { get; }
bool IsReadingPacket { get; }
Task ConnectAsync(CancellationToken cancellationToken);
Task DisconnectAsync(CancellationToken cancellationToken);

Some files were not shown because too many files have changed in this diff Show More