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 2aa4219838 Expose server session items (#1638)
* Expose server session items

* Add support for custom session items for injected application messages
2023-01-19 20:15:15 +01:00
Günther Foidl b98417ab39 Avoid some full memory barriers (#1590)
* Avoid some full memory barriers

* Reset in stats itself

* Updated MqttClientStatistics

* Fixed order of members as artifact from merge-conflict
2023-01-19 20:08:51 +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 ead58dd606 Update ci.yml 2022-12-28 13:59:05 +01:00
Christian a93e935585 Update ci.yml 2022-12-28 09:37:01 +01:00
Christian aeb07e880e Update ReleaseNotes.md 2022-12-28 09:25:33 +01:00
Jeffery Michael e0ce1f309e dotnet 7 packages (#1636)
* Added dotnet 7 support/builds.

* Added NETCOREAPP_3_1_OR_GREATER where applicable.
2022-12-28 09:20:11 +01:00
Christian d74c81c3d1 Fix unit tests 2022-12-23 20:01:13 +01:00
Christian 2fa7200fb3 1544 add underlying socketoption of tcp keepalive into the server option (#1606)
* Expose TCP keep alive options to options builder

* Update ReleaseNotes.md
2022-12-23 17:15:49 +01:00
Christian 9f7d15e1b1 Update ReleaseNotes.md 2022-12-23 17:13:25 +01:00
Christian fdf00f1b18 More options for disconnect (#1557)
* Expose more options in disconnect options.

* Expose more options in disconnect API.

* Refactor code.

* Expose user properties in disconnected event.

* Expose more disconnect options in server.

* Fix build errors

* Add new samples.

* Fix client cleanup

* Fix merge conflicts
2022-12-23 17:09:02 +01:00
ghord abe7bbda15 Pass correct clientId to Unsubscribe interceptor (#1631) 2022-12-23 17:02:03 +01:00
ChristianandHansM 795139d78f Add feature validations (#1527)
* Add feature validation.

* Update ReleaseNotes.md

* Add validators or subscribe and unsubscribe.

* Add feature validation for disconnect.

* Fix build errors.

* Fix build errors

Co-authored-by: HansM <HansM2013@gmx.de>
2022-12-23 16:56:11 +01:00
Christian c7390ef0d7 Update verison 2022-12-23 16:51:13 +01:00
老九andGünther Foidl 8a926a3db3 Performance optimization of SendPacketAsync (#1624)
* performance optimization of SendPacketAsync

* Update Source/MQTTnet.AspnetCore/MqttConnectionContext.cs

Co-authored-by: Günther Foidl <gue@korporal.at>

Co-authored-by: Günther Foidl <gue@korporal.at>
2022-12-16 17:13:11 +01:00
soyoo 7d5e49b44c A typo in comment. (#1628) 2022-12-16 17:12:46 +01:00
Christian 555c3eac7a Reset release notes 2022-12-13 17:15:43 +01:00
Christian f79649d71b 1612 mqttserver subscription to similar base and subtopic with wildcards fails (#1622) 2022-12-07 20:59:52 +01:00
dependabot[bot] c9c27b9f01 Bump Newtonsoft.Json from 13.0.1 to 13.0.2 in /Source/MQTTnet.TestApp (#1621)
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-07 20:57:29 +01:00
Christian 0bc47cf853 Improve unit tests 2022-12-07 20:50:34 +01:00
Christian 5649328bc5 Update release notes 2022-12-07 20:44:30 +01:00
Christian 726dd15c5a Cleanup projects 2022-12-07 20:44:23 +01:00
Christian d513c6f85c Refactor code 2022-12-07 19:43:10 +01:00
老九 c1c17ba162 MqttConnectionContext: Adapting mqtt over tcp (#1616)
Adapting mqtt over tcp.
2022-12-07 19:36:13 +01:00
soyoo f8aa686557 A typo in comment. (#1618) 2022-12-07 17:08:15 +01:00
Christian d7eb4bbe6e Allow interception of will messages (#1613) 2022-12-06 20:28:01 +01:00
Christian c17a350796 1595 object disposed exception in unit test handle parallel connection attempts (#1610) 2022-12-05 19:22:18 +01:00
Christian bb106f8c6e Refactor code 2022-12-05 19:21:08 +01:00
老九 09e47fb5eb Avoid a split MqttPacketBuffer transfer caused by triggering PipeWriter.WriteAsync twice. (#1609)
Avoid a split MqttPacketBuffer transfer caused by triggering PipeWriter.FlushAsync twice.
2022-12-05 19:19:31 +01:00
Christian 099209fff2 Update README.md 2022-12-04 14:10:54 +01:00
Christian d572490a67 Use the net452 supporting async task completion source (#1607) 2022-12-04 13:39:50 +01:00
Christian 180e7f79d4 Update ci.yml 2022-12-03 14:50:30 +01:00
Christian d9da7cc92e Refactor code 2022-12-03 14:09:27 +01:00
Christian c2ce2044cc Merge branch 'master' of https://github.com/dotnet/MQTTnet 2022-12-03 13:19:38 +01:00
Christian e400bfb79e Update ci.yml 2022-12-03 13:19:37 +01:00
老九andChristian 3d32faa8f9 Avoid copying buffer from ReadOnlySequence as ReceivedMqttPacket body (#1596)
* Avoid copying buffer from ReadOnlySequence as  ReceivedMqttPacket body

* Use ReadOnlySequence.FirstSpan if possible.

* add ReaderExtensionsBenchmark

* update benchmark

* fix fixedHeader incorrect read

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2022-12-03 13:18:30 +01:00
老九andChristian 862d76471b Implement buffer copy using different policies (#1599)
use Memory-Copy

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2022-12-03 13:17:02 +01:00
老九 27f2474876 fix MqttBufferReader #1597 (#1598) 2022-12-03 13:14:07 +01:00
Christian 01a5aeeb58 Refactor benchmarks (#1601) 2022-11-30 18:09:34 +01:00
老九 47df131e7f Avoid to check the type of propertyWriter parameter (#1600)
Refactor code of MqttBufferWriter
2022-11-30 17:56:05 +01:00
Christian 56750adfe4 1592 bug of mqttbufferreader (#1593) 2022-11-29 20:23:20 +01:00
Christian 11e64ef018 Update ReleaseNotes.md 2022-11-28 21:10:46 +01:00
Christian 9b20e0a430 Refactor code 2022-11-28 21:10:42 +01:00
老九 41df2ec5da Optimize SendPacketAsync method for MQTTnet.AspNetCore (#1582)
* Avoid to call MqttPacketBuffer.Join() when SendPacketAsync

* Safely cleanup the PacketFormatterAdapter

* Keep buffer.Join when mqtt over webSocket

* null checking for IHttpContextFeature.HttpContext
2022-11-28 21:02:13 +01:00
Christian e45b207375 Update ReleaseNotes.md 2022-11-26 14:53:03 +01:00
Christian c37866e534 Refactor code and update release notes 2022-11-26 14:51:36 +01:00
老九 bda4ab7025 Set string.Empty as default value for SenderClientId (#1583) 2022-11-26 14:50:08 +01:00
Christian 32cc1ff7b1 Refactor code and update release notes 2022-11-26 14:49:28 +01:00
Dalibor Kundrat b5952a590a Extend server client ResetStatistics() to reset all client statistics not only channel... (#1587)
Extend reset client statistic to reset all stats
2022-11-26 14:47:17 +01:00
Christian 581a024ceb Improve allocations for AsyncSignal. 2022-11-26 14:45:34 +01:00
Mario ZelgerandMario Zelger c45cb8b04e Dispose the AsyncSignalWaiter before setting it to null (#1586)
dispose the AsyncSignalWaiter before setting it to null to prevent memory leak

Co-authored-by: Mario Zelger <mario.zelger@confer.ch>
2022-11-26 14:19:51 +01:00
Christian a5e1a4d6dd Update version 2022-11-26 14:16:37 +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
180 changed files with 5991 additions and 2297 deletions
+8 -1
View File
@@ -1 +1,8 @@
* [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).
+3 -6
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.1.3.${{github.run_number}}"
VERSION: "4.2.0.${{github.run_number}}"
jobs:
build:
@@ -22,6 +22,7 @@ jobs:
dotnet-version: |
3.1.x
6.0.x
7.0.x
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
@@ -60,8 +61,4 @@ jobs:
- name: Publish MyGet nugets
if: ${{ github.event_name == 'push' }}
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
# - name: Publish nugets
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
+76 -2
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>
@@ -240,8 +308,14 @@ 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/=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>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREC/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREL/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=SUBACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -1
View File
@@ -4,7 +4,7 @@
</p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg)
[![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+26 -4
View File
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
public static class Client_Connection_Samples
{
public static async Task Clean_Disconnect()
{
/*
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
* to the server and close the connection afterwards.
*
* See sample _Connect_Client_ for more details.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
// connection is closed in a "not clean" way. See MQTT specification for more details.
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
}
}
public static async Task Connect_Client()
{
/*
@@ -138,7 +160,7 @@ public static class Client_Connection_Samples
{
/*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms.
*
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
@@ -232,7 +254,7 @@ public static class Client_Connection_Samples
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
}
}
@@ -297,7 +319,7 @@ public static class Client_Connection_Samples
/*
* This sample sends a PINGREQ packet to the server and waits for a reply.
*
* This is only supported in METTv5.0.0+.
* This is only supported in MQTTv5.0.0+.
*/
var mqttFactory = new MqttFactory();
@@ -386,4 +408,4 @@ public static class Client_Connection_Samples
});
}
}
}
}
+1 -3
View File
@@ -9,12 +9,10 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
@@ -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}");
}
}
}
@@ -0,0 +1,53 @@
// 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.Internal;
using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
// ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local
public static class Server_Diagnostics_Samples
{
public static async Task Get_Notified_When_Client_Received_Message()
{
/*
* This sample starts a MQTT server and attaches an event which gets fired whenever
* a client received and acknowledged a PUBLISH packet.
*/
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
// Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
// It is also possible to read additional data from the client response. This requires casting the response packet.
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
}
}
@@ -12,8 +12,6 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
@@ -18,7 +18,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Helpers;
namespace MQTTnet.AspNetCore.Tests
{
@@ -35,7 +35,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
@@ -8,17 +8,15 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\"/>
<Folder Include="wwwroot\" />
</ItemGroup>
</Project>
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
}
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NETCOREAPP3_1_OR_GREATER
public override ValueTask DisposeAsync()
#else
public Task DisposeAsync()
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NETCOREAPP3_1_OR_GREATER
return base.DisposeAsync();
}
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NETCOREAPP3_1_OR_GREATER
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
@@ -32,7 +32,8 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
@@ -50,11 +51,11 @@
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup>
@@ -2,38 +2,59 @@
// 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.IO.Pipelines;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.IO.Pipelines;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Packets;
namespace MQTTnet.AspNetCore
{
public sealed class MqttConnectionContext : IMqttChannelAdapter
{
readonly ConnectionContext _connection;
readonly AsyncLock _writerLock = new AsyncLock();
PipeReader _input;
PipeWriter _output;
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
if (Connection.Transport != null)
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
public long BytesReceived { get; private set; }
public long BytesSent { get; private set; }
public X509Certificate2 ClientCertificate
{
get
{
_input = Connection.Transport.Input;
_output = Connection.Transport.Output;
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{
return tlsFeature.ClientCertificate;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
return httpFeature?.HttpContext?.Connection.ClientCertificate;
}
}
@@ -41,47 +62,59 @@ namespace MQTTnet.AspNetCore
{
get
{
#if NETCOREAPP3_1
if (Connection?.RemoteEndPoint != null)
// mqtt over tcp
#if NETCOREAPP3_1_OR_GREATER
if (_connection.RemoteEndPoint != null)
{
return Connection.RemoteEndPoint.ToString();
return _connection.RemoteEndPoint.ToString();
}
#endif
var connection = Http?.HttpContext?.Connection;
if (connection == null)
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.RemoteIpAddress != null)
{
return Connection.ConnectionId;
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
}
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
return null;
}
}
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
public ConnectionContext Connection { get; }
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public long BytesSent { get; set; }
public long BytesReceived { get; set; }
public bool IsReadingPacket { get; private set; }
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
public bool IsSecureConnection
{
get
{
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{
return true;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
if (httpFeature?.HttpContext != null)
{
return httpFeature.HttpContext.Request.IsHttps;
}
return false;
}
}
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (Connection is TcpConnection tcp && !tcp.IsConnected)
if (_connection is TcpConnection tcp && !tcp.IsConnected)
{
await tcp.StartAsync().ConfigureAwait(false);
}
_input = Connection.Transport.Input;
_output = Connection.Transport.Output;
_input = _connection.Transport.Input;
_output = _connection.Transport.Output;
}
public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -92,16 +125,19 @@ namespace MQTTnet.AspNetCore
return Task.CompletedTask;
}
public void Dispose()
{
_writerLock.Dispose();
}
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{
var input = Connection.Transport.Input;
try
{
while (!cancellationToken.IsCancellationRequested)
{
ReadResult readResult;
var readTask = input.ReadAsync(cancellationToken);
var readTask = _input.ReadAsync(cancellationToken);
if (readTask.IsCompleted)
{
readResult = readTask.Result;
@@ -141,15 +177,15 @@ namespace MQTTnet.AspNetCore
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
_input.AdvanceTo(consumed, observed);
}
}
}
catch (Exception e)
catch (Exception exception)
{
// completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(e);
_output?.Complete(e);
_input?.Complete(exception);
_output?.Complete(exception);
throw;
}
finally
@@ -169,25 +205,44 @@ namespace MQTTnet.AspNetCore
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
var formatter = PacketFormatterAdapter;
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory();
var output = _output;
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
if (result.IsCompleted)
try
{
BytesSent += msg.Length;
}
var buffer = PacketFormatterAdapter.Encode(packet);
PacketFormatterAdapter.Cleanup();
if (buffer.Payload.Count == 0)
{
// zero copy
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
}
else
{
WritePacketBuffer(_output, buffer);
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
}
BytesSent += buffer.Length;
}
finally
{
PacketFormatterAdapter.Cleanup();
}
}
}
public void Dispose()
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
output.Advance(buffer.Length);
}
}
}
}
+50 -23
View File
@@ -2,21 +2,23 @@
// 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.Buffers;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore
{
public static class ReaderExtensions
{
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
return false;
}
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
{
return false;
}
var fixedHeader = copy.First.Span[0];
copy = copy.Slice(headerLength);
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = bodySlice.GetMemory().ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
var bodySegment = GetArraySegment(ref bodySlice);
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
return true;
}
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
{
return input.First;
return segment;
}
// Should be rare
return input.ToArray();
var array = input.ToArray();
return new ArraySegment<byte>(array);
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
if (valueSequence.IsSingleSegment)
{
var valueSpan =
#if NET5_0_OR_GREATER
valueSequence.FirstSpan;
#else
valueSequence.First.Span;
#endif
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
else
{
Span<byte> valueSpan = stackalloc byte[8];
valueSequence.CopyTo(valueSpan);
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
}
static bool TryReadBodyLength(ReadOnlySpan<byte> span, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
var index = 1;
headerLength = 0;
bodyLength = 0;
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
do
{
if (index == temp.Length)
if (index == span.Length)
{
return false;
}
encodedByte = temp[index];
encodedByte = span[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
ThrowProtocolViolationException(span, index);
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index;
bodyLength = value;
return true;
}
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
}
}
}
@@ -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.
namespace MQTTnet.Benchmarks
{
public abstract class BaseBenchmark
@@ -2,54 +2,25 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using System;
using System.IO;
using System.Threading;
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark
public sealed class ChannelAdapterBenchmark : BaseBenchmark
{
MqttChannelAdapter _channelAdapter;
int _iterations;
MemoryStream _stream;
MqttPublishPacket _packet;
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
MemoryStream _stream;
[Benchmark]
public void Receive_10000_Messages()
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
_stream.Position = 0;
}
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
return buffer.ToArray();
}
}
}
}
@@ -1,22 +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 BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Validators;
namespace MQTTnet.Benchmarks.Configurations
{
/// <summary>
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
/// https://benchmarkdotnet.org/Configs/Configs.htm
/// </summary>
public class AllowNonOptimized : BaseConfig
{
public AllowNonOptimized()
{
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
AddJob(Job.InProcess);
}
}
}
@@ -1,19 +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 BenchmarkDotNet.Configs;
using System.Linq;
namespace MQTTnet.Benchmarks.Configurations
{
public class BaseConfig : ManualConfig
{
public BaseConfig()
{
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
}
}
}
@@ -1,19 +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 BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.CsProj;
namespace MQTTnet.Benchmarks.Configurations
{
public class RuntimeCompareConfig : BaseConfig
{
public RuntimeCompareConfig()
{
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark
public class LoggerBenchmark : BaseBenchmark
{
MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger;
@@ -1,14 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType>
<DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -24,11 +26,12 @@
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
</ItemGroup>
@@ -0,0 +1,40 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MemoryCopyBenchmark
{
const int max_length = 1024 * 8;
private byte[] source;
private byte[] target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; }
[GlobalSetup]
public void Setup()
{
source = new byte[max_length];
target = new byte[max_length];
}
[Benchmark(Baseline = true)]
public void Array_Copy()
{
Array.Copy(source, 0, target, 0, Length);
}
[Benchmark]
public void Memory_Copy()
{
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
}
}
}
@@ -1,5 +1,8 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System;
@@ -8,14 +11,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
/// <summary>
/// Create a number of topics, publish, subscribe, and wait for response
/// </summary>
[MemoryDiagnoser]
public class MessageDeliveryBenchmark
public class MessageDeliveryBenchmark : BaseBenchmark
{
List<MqttApplicationMessage> _topicPublishMessages;
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MessageProcessingBenchmark
public class MessageProcessingBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
{
IWebHost _host;
IMqttClient _mqttClient;
@@ -0,0 +1,43 @@
// 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.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttBufferReaderBenchmark
{
byte[] _buffer;
int _bufferLength;
[GlobalSetup]
public void GlobalSetup()
{
var writer = new MqttBufferWriter(1024, 1024);
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
_buffer = writer.GetBuffer();
_bufferLength = writer.Length;
}
[Benchmark]
public void Use_Span()
{
var span = _buffer.AsSpan(0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark]
public void Use_Encoding()
{
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
}
}
}
@@ -1,7 +1,10 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore;
using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups;
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
{
readonly byte[] _demoPayload = new byte[1024];
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public sealed class MqttTcpChannelBenchmark
public class MqttTcpChannelBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttChannel _serverChannel;
+122 -67
View File
@@ -3,85 +3,140 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using MQTTnet.Benchmarks.Configurations;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
Console.WriteLine("--------------------------------------------------------");
Console.WriteLine("1 = MessageProcessingBenchmark");
Console.WriteLine("2 = SerializerBenchmark");
Console.WriteLine("3 = LoggerBenchmark");
Console.WriteLine("4 = TopicFilterComparerBenchmark");
Console.WriteLine("5 = ChannelAdapterBenchmark");
Console.WriteLine("6 = MqttTcpChannelBenchmark");
Console.WriteLine("7 = TcpPipesBenchmark");
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
Console.WriteLine("9 = ServerProcessingBenchmark");
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
Console.WriteLine("b = RoundtripBenchmark");
Console.WriteLine("c = SubscribeBenchmark");
Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark");
Console.WriteLine("f = AsyncLockBenchmark");
static int _selectedBenchmarkIndex;
static List<Type> _benchmarks;
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
public static void Main(string[] arguments)
{
_benchmarks = CollectBenchmarks();
HandleArguments(arguments);
while (true)
{
case '1':
BenchmarkRunner.Run<MessageProcessingBenchmark>();
break;
case '2':
BenchmarkRunner.Run<SerializerBenchmark>();
break;
case '3':
BenchmarkRunner.Run<LoggerBenchmark>();
break;
case '4':
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
break;
case '5':
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
break;
case '6':
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
break;
case '7':
BenchmarkRunner.Run<TcpPipesBenchmark>();
break;
case '8':
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
break;
case '9':
BenchmarkRunner.Run<ServerProcessingBenchmark>();
break;
case 'a':
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
break;
case 'b':
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
break;
case 'c':
BenchmarkRunner.Run<SubscribeBenchmark>();
break;
case 'd':
BenchmarkRunner.Run<UnsubscribeBenchmark>();
break;
case 'e':
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
break;
case 'f':
BenchmarkRunner.Run<AsyncLockBenchmark>();
RenderMenu();
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{
_selectedBenchmarkIndex++;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = 0;
}
}
else if (key.Key == ConsoleKey.UpArrow)
{
_selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
else if (key.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
else if (key.Key == ConsoleKey.Enter)
{
break;
}
}
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static List<Type> CollectBenchmarks()
{
var benchmarks = new List<Type>();
var types = typeof(Program).Assembly.GetExportedTypes();
foreach (var type in types)
{
if (typeof(BaseBenchmark).IsAssignableFrom(type))
{
if (type != typeof(BaseBenchmark))
{
benchmarks.Add(type);
}
}
}
return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
{
return;
}
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
{
_selectedBenchmarkIndex = benchmarkIndex;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
static void RenderMenu()
{
Console.Clear();
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection");
Console.WriteLine("Press Enter for benchmark execution");
Console.WriteLine("Press Esc for exit");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
for (var index = 0; index < _benchmarks.Count; index++)
{
var benchmark = _benchmarks[index];
if (_selectedBenchmarkIndex == index)
{
Console.Write("-> ");
}
else
{
Console.Write(" ");
}
Console.WriteLine(benchmark.Name);
}
Console.WriteLine();
}
}
}
}
@@ -0,0 +1,240 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
MqttPacketFormatterAdapter mqttPacketFormatter;
MemoryStream stream;
[GlobalSetup]
public void GlobalSetup()
{
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder()
.WithTopic("topic")
.WithPayload(new byte[10 * 1024])
.Build();
var packet = MqttPacketFactories.Publish.Create(mqttMessage);
var buffer = mqttPacketFormatter.Encode(packet);
stream = new MemoryStream();
stream.Write(buffer.Packet);
stream.Write(buffer.Payload);
mqttPacketFormatter.Cleanup();
}
[Benchmark(Baseline = true)]
public async Task Before()
{
stream.Position = 0;
var input = PipeReader.Create(stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
[Benchmark]
public async Task After()
{
stream.Position = 0;
var input = PipeReader.Create(stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
public static class ReaderExtensions_Before
{
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
packet = null;
consumed = input.Start;
observed = input.End;
bytesRead = 0;
var copy = input;
if (copy.Length < 2)
{
return false;
}
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{
return false;
}
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = GetMemory(bodySlice).ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
}
packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
bytesRead = headerLength + bodyLength;
return true;
}
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
{
return input.First;
}
// Should be rare
return input.ToArray();
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
var value = 0;
byte encodedByte;
var index = 1;
headerLength = 0;
bodyLength = 0;
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
do
{
if (index == temp.Length)
{
return false;
}
encodedByte = temp[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index;
bodyLength = value;
return true;
}
}
}
}
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark
public class RoundtripProcessingBenchmark : BaseBenchmark
{
[GlobalSetup]
public void GlobalSetup()
@@ -0,0 +1,68 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter;
using System;
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark
{
MemoryStream stream;
MqttPacketBuffer buffer;
[GlobalSetup]
public void GlobalSetup()
{
stream = new MemoryStream(1024);
var packet = new ArraySegment<byte>(new byte[10]);
buffer = new MqttPacketBuffer(packet);
}
[Benchmark(Baseline = true)]
public async ValueTask Before()
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
WritePacketBuffer(output, buffer);
await output.FlushAsync();
}
[Benchmark]
public async ValueTask After()
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
if (buffer.Payload.Count == 0)
{
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
}
else
{
WritePacketBuffer(output, buffer);
await output.FlushAsync().ConfigureAwait(false);
}
}
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
output.Advance(buffer.Length);
}
}
}
@@ -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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups;
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark
public class ServerProcessingBenchmark : BaseBenchmark
{
[GlobalSetup]
public void GlobalSetup()
@@ -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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
@@ -8,7 +12,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public class SubscribeBenchmark
public class SubscribeBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class TcpPipesBenchmark
public class TcpPipesBenchmark : BaseBenchmark
{
IDuplexPipe _client;
IDuplexPipe _server;
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark
public class TopicFilterComparerBenchmark : BaseBenchmark
{
static readonly char[] TopicLevelSeparator = { '/' };
+18 -15
View File
@@ -1,31 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MQTTnet.Benchmarks
{
public class TopicGenerator
{
public static void Generate(
int numPublishers, int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
)
int numPublishers,
int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
{
topicsByPublisher = new Dictionary<string, List<string>>();
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
{
topicsPerLevel = 1;
}
int numLevel1Topics = topicsPerLevel;
int numLevel2Topics = topicsPerLevel;
var numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
for (var p = 0; p < numPublishers; ++p)
{
int publisherTopicCount = 0;
var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{
if (publisherTopicCount >= numTopicsPerPublisher)
{
break;
}
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if ((l1 == 0) && (l3 == 0))
if (l1 == 0 && l3 == 0)
{
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
}
}
}
}
@@ -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 BenchmarkDotNet.Attributes;
using MQTTnet.Client;
using MQTTnet.Server;
@@ -7,7 +11,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public class UnsubscribeBenchmark
public class UnsubscribeBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
-54
View File
@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
<package id="System.Console" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
</packages>
@@ -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);
}
}
@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -33,7 +34,7 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -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,10 +17,10 @@ using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
public sealed class ManagedMqttClient : Disposable
{
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>();
@@ -76,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);
@@ -118,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;
@@ -164,7 +170,7 @@ namespace MQTTnet.Extensions.ManagedClient
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
ManagedMqttApplicationMessage removedMessage = null;
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
ApplicationMessageDroppedEventArgs applicationMessageDroppedEventArgs = null;
try
{
@@ -175,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;
}
@@ -183,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);
}
}
@@ -202,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);
}
}
}
@@ -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);
}
}
}
@@ -10,24 +10,54 @@ namespace MQTTnet.Extensions.ManagedClient
{
public static class MqttFactoryExtensions
{
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient, IMqttNetLogger logger)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
return new NewManagedMqttClient(factory.CreateMqttClient(), logger);
}
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
return new NewManagedMqttClient(mqttClient, logger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new NewManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
}
return new NewManagedMqttClient(mqttClient, factory.DefaultLogger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
return new NewManagedMqttClient(factory.CreateMqttClient(logger), logger);
}
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; }
}
}
}
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
{
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PacketIdentifier = packetIdentifier;
}
public ManagedMqttApplicationMessage ApplicationMessage { get; }
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,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; }
}
}
@@ -0,0 +1,15 @@
// 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.
namespace MQTTnet.Extensions.ManagedClient
{
public enum MaintainConnectionResult
{
NoChange,
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);
@@ -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,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
@@ -33,7 +33,8 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
{
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
{
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
return factory.CreateMqttRpcClient(
mqttClient,
new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
}
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
+42 -46
View File
@@ -2,23 +2,25 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClient : IMqttRpcClient
{
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -27,6 +29,18 @@ namespace MQTTnet.Extensions.Rpc
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
@@ -49,15 +63,13 @@ namespace MQTTnet.Extensions.Rpc
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
{
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
if (methodName == null)
{
MethodName = methodName,
QualityOfServiceLevel = qualityOfServiceLevel,
MqttClient = _mqttClient,
Options = _options
});
throw new ArgumentNullException(nameof(methodName));
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
@@ -72,34 +84,34 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessage = new MqttApplicationMessageBuilder()
.WithTopic(requestTopic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithResponseTopic(responseTopic)
.Build();
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
#if NET452
var awaitable = new TaskCompletionSource<byte[]>();
#else
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
#endif
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(responseTopic, qualityOfServiceLevel)
.Build();
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
using (cancellationToken.Register(
() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
@@ -107,8 +119,8 @@ namespace MQTTnet.Extensions.Rpc
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -119,28 +131,12 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance;
}
#if NET452
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
#else
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
#endif
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
}
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
}
}
}
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
{
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public MqttRpcClientOptions Build()
{
return new MqttRpcClientOptions
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
TopicGenerationStrategy = _topicGenerationStrategy
};
}
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
}
}
}
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{
throw new ArgumentException("The method name cannot contain /, + or #.");
@@ -2,6 +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 MQTTnet.Client;
using MQTTnet.Protocol;
@@ -9,20 +10,29 @@ namespace MQTTnet.Extensions.Rpc
{
public sealed class TopicGenerationContext
{
public string MethodName { get; set; }
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
public string MethodName { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
/// that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public IMqttClient MqttClient { get; set; }
public MqttRpcClientOptions Options { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
@@ -33,7 +33,8 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
{
if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
if (mqttFactory == null)
{
throw new ArgumentNullException(nameof(mqttFactory));
}
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
}
}
}
}
@@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Security.Authentication;
@@ -17,14 +18,14 @@ using MQTTnet.Exceptions;
using MQTTnet.Internal;
using SuperSocket.ClientEngine;
using WebSocket4Net;
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
namespace MQTTnet.Extensions.WebSocket4Net
{
public sealed class WebSocket4NetMqttChannel : IMqttChannel
{
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
readonly MqttClientOptions _clientOptions;
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
readonly MqttClientWebSocketOptions _webSocketOptions;
WebSocket _webSocket;
@@ -35,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
}
public X509Certificate2 ClientCertificate { get; }
public string Endpoint => _webSocketOptions.Uri;
public bool IsSecureConnection { get; private set; }
public X509Certificate2 ClientCertificate { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
public Task ConnectAsync(CancellationToken cancellationToken)
{
var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
@@ -55,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
uri = "wss://" + uri;
}
}
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
#if NET48 || NETCOREAPP3_0_OR_GREATER
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
#else
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
@@ -90,7 +93,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
var receiveBufferSize = 0;
var certificates = new X509CertificateCollection();
if (_webSocketOptions?.TlsOptions?.Certificates != null)
if (_webSocketOptions.TlsOptions?.Certificates != null)
{
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
{
@@ -107,15 +110,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
NoDelay = true,
Security =
{
AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
Certificates = certificates
}
};
await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
return ConnectInternalAsync(cancellationToken);
}
public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -124,10 +125,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
_webSocket.Close();
}
return CompletedTask.Instance;
}
public void Dispose()
{
if (_webSocket == null)
{
return;
}
_webSocket.DataReceived -= OnDataReceived;
_webSocket.Error -= OnError;
_webSocket.Dispose();
_webSocket = null;
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var readBytes = 0;
@@ -161,75 +175,55 @@ namespace MQTTnet.Extensions.WebSocket4Net
return CompletedTask.Instance;
}
public void Dispose()
{
if (_webSocket == null)
{
return;
}
_webSocket.DataReceived -= OnDataReceived;
_webSocket.Error -= OnError;
_webSocket.Dispose();
_webSocket = null;
}
static void OnError(object sender, ErrorEventArgs e)
{
System.Diagnostics.Debug.Write(e.Exception.ToString());
}
void OnDataReceived(object sender, DataReceivedEventArgs e)
{
foreach (var @byte in e.Data)
{
_receiveBuffer.Add(@byte);
}
}
async Task ConnectInternalAsync(CancellationToken cancellationToken)
{
_webSocket.Error += OnError;
_webSocket.DataReceived += OnDataReceived;
var taskCompletionSource = new TaskCompletionSource<Exception>();
var promise = new AsyncTaskCompletionSource<bool>();
void ErrorHandler(object sender, ErrorEventArgs e)
{
taskCompletionSource.TrySetResult(e.Exception);
promise.TrySetException(e.Exception);
}
void SuccessHandler(object sender, EventArgs e)
void OpenedHandler(object sender, EventArgs e)
{
taskCompletionSource.TrySetResult(null);
promise.TrySetResult(true);
}
void ClosedHandler(object sender, EventArgs e)
{
promise.TrySetException(new MqttCommunicationException("Connection closed."));
}
try
{
_webSocket.Opened += SuccessHandler;
_webSocket.Opened += OpenedHandler;
_webSocket.Closed += ClosedHandler;
_webSocket.Error += ErrorHandler;
#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
_webSocket.Open();
#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
{
using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
{
try
{
await taskCompletionSource.Task.ConfigureAwait(false);
// OpenAsync does basically the same but without support for a cancellation token.
// So we do it on our own!
_webSocket.Open();
await promise.Task.ConfigureAwait(false);
}
catch (Exception exception)
{
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
if (exception is AuthenticationException authenticationException)
{
throw new MqttCommunicationException(authenticationException.InnerException);
@@ -251,9 +245,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
}
finally
{
_webSocket.Opened -= SuccessHandler;
_webSocket.Opened -= OpenedHandler;
_webSocket.Closed -= ClosedHandler;
_webSocket.Error -= ErrorHandler;
}
}
void OnDataReceived(object sender, DataReceivedEventArgs e)
{
foreach (var @byte in e.Data)
{
_receiveBuffer.Add(@byte);
}
}
static void OnError(object sender, ErrorEventArgs e)
{
Debug.Write(e.Exception.ToString());
}
}
}
}
@@ -2,12 +2,12 @@
// 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.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using System;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
namespace MQTTnet.Extensions.WebSocket4Net
{
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
switch (options.ChannelOptions)
{
@@ -23,7 +26,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
return new MqttChannelAdapter(
new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector,
logger);
}
@@ -32,16 +35,16 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector,
logger);
}
default:
{
throw new NotSupportedException();
}
{
throw new NotSupportedException();
}
}
}
}
}
}
@@ -1,20 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>
+105 -72
View File
@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using System;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
@@ -17,10 +18,6 @@ namespace MQTTnet.TestApp
{
public static async Task RunAsync()
{
// MqttNetConsoleLogger.ForwardToConsole();
// For most of these connections to work, set output target to Net5.0.
#if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
@@ -31,7 +28,6 @@ namespace MQTTnet.TestApp
CertificateValidationHandler = w => true
};
#endif
// Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
{
@@ -42,33 +38,37 @@ namespace MQTTnet.TestApp
};
// mqtt.eclipseprojects.io
await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"mqtt.eclipseprojects.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(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 ExecuteTestAsync("test.mosquitto.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
.WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org TCP - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
@@ -76,27 +76,40 @@ namespace MQTTnet.TestApp
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12 - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTls(unsafeTls12)
.Build());
await ExecuteTestAsync("test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"test.mosquitto.org WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestAsync(
"test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
// 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 ExecuteTestAsync("broker.emqx.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.emqx.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
@@ -104,23 +117,37 @@ namespace MQTTnet.TestApp
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
await ExecuteTestAsync("broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestAsync(
"broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
true);
// broker.hivemq.com
await ExecuteTestAsync("broker.hivemq.com TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
// mqtt.swifitch.cz: Does not seem to operate any more
@@ -130,36 +157,43 @@ namespace MQTTnet.TestApp
Console.ReadLine();
}
static async Task ExecuteTestAsync(string name, MqttClientOptions options)
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
{
try
{
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
var factory = new MqttFactory();
//factory.UseWebSocket4Net();
var client = factory.CreateMqttClient();
var topic = Guid.NewGuid().ToString();
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceivedAsync += e =>
if (useWebSocket4Net)
{
receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance;
};
await client.ConnectAsync(options);
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
{
throw new Exception("Message invalid.");
factory.UseWebSocket4Net();
}
await client.UnsubscribeAsync(topic);
await client.DisconnectAsync();
using (var client = factory.CreateMqttClient())
{
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance;
};
await client.ConnectAsync(options).ConfigureAwait(false);
var topic = Guid.NewGuid().ToString();
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
{
throw new Exception("Message invalid.");
}
await client.UnsubscribeAsync(topic).ConfigureAwait(false);
await client.DisconnectAsync().ConfigureAwait(false);
}
Write("[OK]\n", ConsoleColor.Green);
}
@@ -174,6 +208,5 @@ namespace MQTTnet.TestApp
Console.ForegroundColor = color;
Console.Write(message);
}
}
}
}
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{
[TestClass]
public sealed class LowLevelMqttClient_Tests : BaseTestClass
@@ -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;
@@ -17,80 +16,20 @@ using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
public sealed class ManagedMqttClient_Connection_Tests : BaseTestClass
{
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod]
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 =>
@@ -117,76 +56,63 @@ namespace MQTTnet.Tests.Client
}
[TestMethod]
public async Task Drop_New_Messages_On_Full_Queue()
public async Task Receive_While_Not_Cleanly_Disconnected()
{
var factory = new MqttFactory();
var managedClient = factory.CreateManagedMqttClient();
try
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var clientOptions = new ManagedMqttClientOptionsBuilder().WithMaxPendingMessages(5)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
await testEnvironment.StartServer(o => o.WithPersistentSessions());
clientOptions.WithClientOptions(o => o.WithTcpServer("localhost"));
var senderClient = await testEnvironment.ConnectClient();
await managedClient.StartAsync(clientOptions.Build());
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
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 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 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 =>
var managedClientOptions = new ManagedMqttClientOptions
{
Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance;
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await receivingClient.SubscribeAsync("My/last/will");
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
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();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
// Wait for arrival of the will message at the receiver.
await Task.Delay(5000);
receivedMessages.AssertReceivedCountEquals(1);
Assert.AreEqual(1, receivedMessagesCount);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
@@ -197,18 +123,18 @@ namespace MQTTnet.Tests.Client
{
var server = await testEnvironment.StartServer();
var managedClient = new 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);
}
@@ -224,7 +150,7 @@ namespace MQTTnet.Tests.Client
await testEnvironment.StartServer();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.CreateManagedClient();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage();
@@ -264,6 +190,8 @@ namespace MQTTnet.Tests.Client
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var unmanagedClient = testEnvironment.CreateClient();
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
@@ -280,7 +208,7 @@ namespace MQTTnet.Tests.Client
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();
@@ -310,7 +238,7 @@ namespace MQTTnet.Tests.Client
await connected;
// wait a bit so that the managed client can reestablish the subscriptions
await Task.Delay(500);
await LongTestDelay();
received = SetupReceivingOfMessages(managedClient, 2);
@@ -326,28 +254,22 @@ namespace MQTTnet.Tests.Client
{
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.PublishAsync(
new MqttApplicationMessage
{
Topic = "topic",
Payload = new byte[] { 1 },
Retain = true
});
await sendingClient.PublishStringAsync("topic", "A", retain: true);
// Wait a bit for the retained message to be available
await LongTestDelay();
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());
managedClient.ApplicationMessageReceivedAsync += e =>
{
@@ -357,17 +279,18 @@ namespace MQTTnet.Tests.Client
await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
var managedClientOptions = testEnvironment.CreateDefaultManagedMqttClientOptions();
managedClientOptions.ClientOptions.CleanSession = false;
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync();
await LongTestDelay();
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
@@ -377,9 +300,8 @@ namespace MQTTnet.Tests.Client
// Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic");
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count);
}
}
@@ -391,12 +313,13 @@ namespace MQTTnet.Tests.Client
{
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", Payload = new byte[] { 1 }, Retain = true });
var subscribeTime = DateTime.UtcNow;
@@ -421,7 +344,9 @@ namespace MQTTnet.Tests.Client
{
using (var testEnvironment = CreateTestEnvironment())
{
var managedClient = await CreateManagedClientAsync(testEnvironment);
await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient();
var sendingClient = await testEnvironment.ConnectClient();
@@ -447,14 +372,52 @@ namespace MQTTnet.Tests.Client
}
}
async Task<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();
@@ -464,7 +427,7 @@ namespace MQTTnet.Tests.Client
// at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.CreateManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient());
var connected = GetConnectedTask(managedClient);
@@ -475,35 +438,20 @@ namespace MQTTnet.Tests.Client
return managedClient;
}
/// <summary>
/// Returns a task that will finish when the
/// <paramref
/// name="managedClient" />
/// has connected
/// </summary>
Task GetConnectedTask(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(ManagedMqttClient managedClient, int expectedNumberOfMessages)
static Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(IManagedMqttClient managedClient, int expectedNumberOfMessages)
{
var receivedMessages = new List<MqttApplicationMessage>();
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -526,7 +474,7 @@ namespace MQTTnet.Tests.Client
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
{
IList<ManagedMqttApplicationMessage> _messages;
IList<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>();
public int GetMessageCount()
{
@@ -535,11 +483,6 @@ namespace MQTTnet.Tests.Client
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);
}
}
}
}
@@ -0,0 +1,162 @@
// 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.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Connection_Tests : BaseTestClass
{
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
}
}
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
public async Task ConnectTimeout_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
var disconnectHandlerCalled = false;
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
Assert.Fail("Must fail!");
}
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
}
await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
}
}
[TestMethod]
public async Task Disconnect_Clean()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
}
[TestMethod]
public async Task Disconnect_Clean_With_Custom_Reason()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
}
}
[TestMethod]
public async Task Disconnect_Clean_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count);
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
}
}
}
}
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Tests : BaseTestClass
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
public async Task Connect_Disconnect_Connect()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
}
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
using (var testEnvironment = CreateTestEnvironment())
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
await testEnvironment.StartServer();
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.DisconnectAsync();
await client.ConnectAsync(clientOptions);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
[ExpectedException(typeof(InvalidOperationException))]
public async Task Connect_Multiple_Times_Should_Fail()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
public async Task ConnectTimeout_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
using (var testEnvironment = CreateTestEnvironment())
{
var disconnectHandlerCalled = false;
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
await testEnvironment.StartServer();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
Assert.Fail("Must fail!");
}
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
}
await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
await client.ConnectAsync(clientOptions);
await client.ConnectAsync(clientOptions);
}
}
@@ -6,9 +6,8 @@ using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Extensions;
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients
{
[TestClass]
public class MqttClientOptionsBuilder_Tests
@@ -6,68 +6,93 @@ using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Tests.Mockups;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests
namespace MQTTnet.Tests.Extensions
{
[TestClass]
public sealed class RPC_Tests : BaseTestClass
public sealed class Rpc_Tests : BaseTestClass
{
[TestMethod]
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod]
public Task Execute_Success_With_QoS_0()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_1()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_2()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod]
public Task Execute_Success_With_QoS_1()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
}
[TestMethod]
public Task Execute_Success_With_QoS_2()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
{
@@ -90,6 +115,31 @@ namespace MQTTnet.Tests
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await CompletedTask.Instance;
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_With_Custom_Topic_Names()
@@ -98,14 +148,24 @@ namespace MQTTnet.Tests
{
await testEnvironment.StartServer();
var requestSender = await testEnvironment.ConnectClient();
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
[TestMethod]
public void Use_Factory()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
var rpcClient = factory.CreateMqttRpcClient(client);
Assert.IsNotNull(rpcClient);
}
}
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
{
using (var testEnvironment = CreateTestEnvironment())
@@ -151,57 +211,6 @@ namespace MQTTnet.Tests
}
}
[TestMethod]
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await CompletedTask.Instance;
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
}
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
@@ -214,4 +223,4 @@ namespace MQTTnet.Tests
}
}
}
}
}
@@ -0,0 +1,51 @@
// 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.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Tests.Helpers;
namespace MQTTnet.Tests.Extensions
{
[TestClass]
public sealed class WebSocket4Net_Tests
{
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_Failed_With_Invalid_Server()
{
var factory = new MqttFactory().UseWebSocket4Net();
using (var client = factory.CreateMqttClient())
{
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
await client.ConnectAsync(options).ConfigureAwait(false);
}
}
[TestMethod]
public void Use_Correct_Adapter()
{
// Should not throw exception
var factory = new MqttFactory().UseWebSocket4Net();
using (var client = factory.CreateMqttClient())
{
var adapterFactory = client.GetFieldValue("_adapterFactory");
Assert.IsInstanceOfType(adapterFactory, typeof(WebSocket4NetMqttClientAdapterFactory));
}
}
[TestMethod]
public void Use_WebSocket4Net()
{
// Should not throw exception
new MqttFactory().UseWebSocket4Net();
}
}
}
@@ -0,0 +1,310 @@
// 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.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter
{
[TestClass]
public sealed class MqttBufferReader_Tests
{
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
public void Fire_Exception_If_Not_Enough_Data()
{
var buffer = new byte[] { 0, 1, 2 };
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, 3);
// 1 byte is missing.
reader.ReadFourByteInteger();
}
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, 3);
// 1 byte is missing.
reader.ReadFourByteInteger();
}
[TestMethod]
public void Is_EndOfStream_Without_Buffer()
{
var reader = new MqttBufferReader();
Assert.IsTrue(reader.EndOfStream);
Assert.AreEqual(0, reader.BytesLeft);
}
[TestMethod]
public void Read_Remaining_Data_From_Larger_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
// The used buffer contains more data than used!
reader.SetBuffer(buffer, 0, 5);
// This should only read 5 bytes even if more data is in the buffer
// due to custom bounds.
var remainingData = reader.ReadRemainingData();
Assert.IsTrue(reader.EndOfStream);
Assert.AreEqual(0, reader.BytesLeft);
Assert.AreEqual(5, remainingData.Length);
}
[TestMethod]
public void Read_Various_Positions_and_Offsets()
{
const int NumBufferElements = 1000;
const int NumTestSequences = 10000;
const string TestString = "The quick brown fox jumps over the lazy dog.";
var rndSeed = Environment.TickCount;
// rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
var rnd = new Random(rndSeed);
// rndSeed is printed when an assert fails
var bufferElements = new List<ElementReference>();
byte[] elementBuffer;
// Fill a buffer with strings or numbers in big endian format
using (var ms = new MemoryStream())
{
using (var bw = new BinaryWriter(ms))
{
var bufferOffset = 0;
for (var i = 0; i < NumBufferElements; i++)
{
var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
byte[] elementBytes = null;
uint elementNumberValue = 0; // all element number values fit into uint
string elementStringValue = null;
var elementSize = 0;
var alreadyBigEndian = false;
switch (nextElementType)
{
case ElementReference.BufferElementType.Byte:
var byteValue = (byte)i;
elementNumberValue = byteValue;
elementSize = sizeof(byte);
elementBytes = new byte[1] { byteValue };
alreadyBigEndian = true; // nothing to swap
break;
case ElementReference.BufferElementType.TwoByteInt:
var ushortValue = (ushort)i;
elementNumberValue = ushortValue;
elementSize = sizeof(ushort);
elementBytes = BitConverter.GetBytes(ushortValue);
break;
case ElementReference.BufferElementType.FourByteInt:
var uintValue = (uint)i;
elementNumberValue = uintValue;
elementSize = sizeof(uint);
elementBytes = BitConverter.GetBytes(uintValue);
break;
case ElementReference.BufferElementType.VariableSizeInt:
{
elementNumberValue = (uint)i;
var writer = new MqttBufferWriter(4, 4);
writer.WriteVariableByteInteger(elementNumberValue);
elementSize = writer.Length;
elementBytes = new byte[elementSize];
var buffer = writer.GetBuffer();
Array.Copy(buffer, elementBytes, elementSize);
alreadyBigEndian = true; // nothing to swap
}
break;
case ElementReference.BufferElementType.String:
{
var stringLen = rnd.Next(TestString.Length);
elementStringValue = TestString.Substring(0, stringLen); // could be empty
var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
writer.WriteString(elementStringValue);
elementSize = writer.Length;
elementBytes = new byte[elementSize];
var buffer = writer.GetBuffer();
Array.Copy(buffer, elementBytes, elementSize);
alreadyBigEndian = true; // nothing to swap
}
break;
}
if (BitConverter.IsLittleEndian && !alreadyBigEndian)
{
Array.Reverse(elementBytes);
}
bw.Write(elementBytes);
bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
bufferOffset += elementSize;
}
elementBuffer = ms.ToArray();
}
}
for (var i = 0; i < NumTestSequences; i++)
{
var firstElementIndex = rnd.Next(NumBufferElements - 1);
// segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
var lastElementIndex = firstElementIndex + elementCount - 1;
// Use element index to get buffer offsets
var lastElement = bufferElements[lastElementIndex];
var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
var segmentLength = segmentEndPosition - segmentStartPosition;
var reader = new MqttBufferReader();
reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
// read all elements in the buffer segment; values should be as expected
for (var n = 0; n < elementCount; n++)
{
var element = bufferElements[firstElementIndex + n];
uint elementNumberValue = 0;
string elementStringValue = null;
var expectedPosition = element.BufferOffset - segmentStartPosition;
Assert.AreEqual(expectedPosition, reader.Position);
switch (element.Type)
{
case ElementReference.BufferElementType.Byte:
{
elementNumberValue = reader.ReadByte();
}
break;
case ElementReference.BufferElementType.TwoByteInt:
{
elementNumberValue = reader.ReadTwoByteInteger();
}
break;
case ElementReference.BufferElementType.FourByteInt:
{
elementNumberValue = reader.ReadFourByteInteger();
}
break;
case ElementReference.BufferElementType.VariableSizeInt:
{
elementNumberValue = reader.ReadVariableByteInteger();
}
break;
case ElementReference.BufferElementType.String:
{
elementStringValue = reader.ReadString();
}
break;
}
if (elementStringValue != null)
{
Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
}
else
{
Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
}
}
// confirm end of stream
Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
}
}
[TestMethod]
public void Report_Correct_Length_For_Full_Buffer()
{
var buffer = new byte[] { 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, 5);
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
[TestMethod]
public void Report_Correct_Length_For_Partial_End_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
// The used buffer contains more data than used!
reader.SetBuffer(buffer, 5, 5);
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
[TestMethod]
public void Report_Correct_Length_For_Partial_Start_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
// The used buffer contains more data than used!
reader.SetBuffer(buffer, 0, 5);
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
// Helper class to build up a reference to elements of various types in a buffer
class ElementReference
{
public enum BufferElementType
{
Byte,
TwoByteInt,
FourByteInt,
VariableSizeInt,
String
}
public const int NumBufferElementTypes = 5;
public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
{
Type = type;
Size = size;
NumberValue = numberValue;
StringValue = stringValue;
BufferOffset = bufferOffset;
}
public int BufferOffset { get; }
public uint NumberValue { get; }
public int Size { get; }
public string StringValue { get; }
public BufferElementType Type { get; }
}
}
}
@@ -15,7 +15,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Helpers;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Formatter
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Helpers
{
public static class MqttClientExtensions
{
public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
return new TestApplicationMessageReceivedHandler(client);
}
}
}
@@ -5,7 +5,7 @@
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Extensions
namespace MQTTnet.Tests.Helpers
{
public static class MqttPacketWriterExtensions
{
@@ -0,0 +1,28 @@
// 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.Reflection;
namespace MQTTnet.Tests.Helpers
{
public static class ReflectionExtensions
{
public static object GetFieldValue(this object source, string fieldName)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
throw new ArgumentException($"Field {fieldName} not found.");
}
return field.GetValue(source);
}
}
}
+4 -5
View File
@@ -1,27 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
</ItemGroup>
</Project>
+2 -2
View File
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
var c2 = await testEnvironment.ConnectClient(clientOptions);
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent.
await Task.Delay(1000);
await LongTestDelay();
Assert.AreEqual(1, receivedMessagesCount);
}
@@ -27,6 +27,17 @@ namespace MQTTnet.Tests.Mockups
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
}
public int Count
{
get
{
lock (_receivedEventArgs)
{
return _receivedEventArgs.Count;
}
}
}
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
{
get
@@ -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,6 +21,8 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Protocol;
using MQTTnet.Server;
#endregion
namespace MQTTnet.Tests.Mockups
{
public sealed class TestEnvironment : IDisposable
@@ -28,6 +32,7 @@ namespace MQTTnet.Tests.Mockups
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>();
@@ -89,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));
@@ -238,6 +247,21 @@ namespace MQTTnet.Tests.Mockups
}
}
public ManagedMqttClientOptions CreateDefaultManagedMqttClientOptions()
{
return Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(Factory.CreateClientOptionsBuilder().WithTcpServer("127.0.0.1", ServerPort).Build()).Build();
}
public MqttClientOptions CreateDefaultClientOptions()
{
return CreateDefaultClientOptionsBuilder().Build();
}
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
}
public ILowLevelMqttClient CreateLowLevelClient()
{
lock (_clients)
@@ -249,6 +273,21 @@ namespace MQTTnet.Tests.Mockups
}
}
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)
{
if (Server != null)
@@ -260,6 +299,11 @@ namespace MQTTnet.Tests.Mockups
Server = Factory.CreateMqttServer(options, logger);
if (TrackServerReceivedApplicationMessages)
{
Server.InterceptingPublishAsync += ServerOnInterceptingPublishAsync;
}
Server.ValidatingConnectionAsync += e =>
{
if (TestContext != null)
@@ -296,6 +340,18 @@ namespace MQTTnet.Tests.Mockups
}
}
foreach (var managedMqttClient in _managedClients)
{
try
{
managedMqttClient.StopAsync().Wait();
}
finally
{
managedMqttClient.Dispose();
}
}
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
@@ -322,6 +378,13 @@ namespace MQTTnet.Tests.Mockups
}
}
public async Task<IManagedMqttClient> StartManagedClient()
{
var managedClient = Factory.CreateManagedMqttClient(CreateClient());
await managedClient.StartAsync(CreateDefaultManagedMqttClientOptions());
return managedClient;
}
public Task<MqttServer> StartServer()
{
return StartServer(Factory.CreateServerOptionsBuilder());
@@ -391,5 +454,15 @@ namespace MQTTnet.Tests.Mockups
_exceptions.Add(exception);
}
}
Task ServerOnInterceptingPublishAsync(InterceptingPublishEventArgs arg)
{
lock (ServerReceivedApplicationMessages)
{
ServerReceivedApplicationMessages.Add(arg.ApplicationMessage);
}
return CompletedTask.Instance;
}
}
}
@@ -0,0 +1,44 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttApplicationMessageValidator_Tests
{
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Succeed_When_Using_TopicAlias_And_MQTT_311()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311);
}
[TestMethod]
public void Succeed_When_Using_TopicAlias_And_MQTT_500()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500);
}
[TestMethod]
public void Succeed_When_Using_UserProperties_And_MQTT_500()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(
new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(),
MqttProtocolVersion.V500);
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(
new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(),
MqttProtocolVersion.V311);
}
}
}
@@ -0,0 +1,54 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttClientOptionsValidator_Tests
{
[TestMethod]
public void Succeed_When_Using_UserProperties_And_MQTT_500()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
public void Succeed_When_Using_WillUserProperties_And_MQTT_500()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Throw_When_Using_UserProperties_And_MQTT_311()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
}
[TestMethod]
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
}
}
}
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttTopicValidator_Tests
public sealed class MqttTopicValidator_Tests
{
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Plus()
public void Invalid_Topic_Empty()
{
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
MqttTopicValidator.ThrowIfInvalid(string.Empty);
}
[TestMethod]
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Empty()
public void Invalid_Topic_Plus()
{
MqttTopicValidator.ThrowIfInvalid(string.Empty);
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
}
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
}
}
}
+27 -6
View File
@@ -8,27 +8,48 @@ using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
public class Protocol_Tests
public sealed class Protocol_Tests
{
[TestMethod]
public void Encode_Four_Byte_Integer()
{
var writer = new MqttBufferWriter(4, 4);
for (uint value = 0; value < 268435455; value++)
{
writer.WriteVariableByteInteger(value);
var buffer = writer.GetBuffer();
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, writer.Length);
var checkValue = reader.ReadVariableByteInteger();
Assert.AreEqual(value, checkValue);
writer.Reset(0);
}
}
[TestMethod]
public void Encode_Two_Byte_Integer()
{
var writer = new MqttBufferWriter(2, 2);
for (ushort value = 0; value < ushort.MaxValue; value++)
{
writer.WriteTwoByteInteger(value);
var buffer = writer.GetBuffer();
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, writer.Length);
var checkValue = reader.ReadTwoByteInteger();
Assert.AreEqual(value, checkValue);
writer.Reset(0);
}
}
}
}
}
+1 -1
View File
@@ -636,7 +636,7 @@ namespace MQTTnet.Tests.Server
// nothing
Assert.AreEqual(false, c1.IsConnected);
await c1.DisconnectAsync();
await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected);
await Task.Delay(500);
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Internal;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
@@ -60,5 +61,28 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
}
}
[TestMethod]
public async Task Intercept_Injected_Application_Message()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
MqttApplicationMessage interceptedMessage = null;
server.InterceptingPublishAsync += eventArgs =>
{
interceptedMessage = eventArgs.ApplicationMessage;
return CompletedTask.Instance;
};
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
await LongTestDelay();
Assert.IsNotNull(interceptedMessage);
}
}
}
}

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