Compare commits

..
Author SHA1 Message Date
Christian 8ad86928f1 1681 misleading signature of mqttclientconnectasync mqttclientconnectresultcode expected but throws exception (#1749) 2023-05-29 16:29:16 +02:00
Christian d8e5936d45 1724 nullreferenceexception on mqttclientconnectasync (#1748)
Update MqttClient.cs
2023-05-29 13:55:36 +02:00
Christian caa717544b 1742 some bug in mqttclientconnectionvalidatorhandle (#1746) 2023-05-29 13:53:42 +02:00
Christian 66b77f8d6d Merge branch 'master' of https://github.com/dotnet/MQTTnet 2023-05-29 13:47:09 +02:00
Christian ab5264c8ca 1738 userproperties in clientconnectedeventargs (#1747) 2023-05-29 13:45:44 +02:00
Christian db12309bae Update ReleaseNotes.md 2023-05-29 12:41:28 +02:00
Christian 2953e0dcc5 Improve payload to string conversion 2023-05-29 12:41:24 +02:00
Ramon Smits 1b18ac9fe3 Resolve ConvertPayloadToString resulting in ArgumentNullException (#1745)
Resolve breaking change where previously ConvertPayloadToString returned null if the payload was null
2023-05-29 12:38:33 +02:00
Christian 2cdad6622a Fix build 2023-05-14 09:14:37 +02:00
Andrei Kurosh 7601d11a7a UseDefaultCredentials options for WebSockets proxy configuration. (#1734) 2023-05-14 08:58:57 +02:00
Christian 0bb4c6ef09 Expose more TLS options (#1737) 2023-05-13 12:08:52 +02:00
Christian 449624fd72 Expose proper connect result in clients Disconnected event (#1728) 2023-05-06 12:49:41 +02:00
Christian 99f4f46062 Add new overloads for packet payload and will payload (#1721) 2023-05-04 22:19:06 +02:00
b08d11c069 replace lock/semaphore by readerwriterlockslim to avoid lock contenti… (#1716)
* replace lock/semaphore by readerwriterlockslim to avoid lock contention while dispatch messages

* Update MqttClientSubscriptionsManager.cs

removed comment out codes

* Update ReleaseNotes.md

---------

Co-authored-by: Zeheng Feng <hf944813@broadcom.com>
Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-05-01 13:53:15 +02:00
Christian ac41e838ec Merge branch 'master' of https://github.com/dotnet/MQTTnet 2023-05-01 12:20:26 +02:00
Christian 836ddf3bbc Update version 2023-05-01 12:20:20 +02:00
Christian 0e815c626c Add validation of string lengths (#1718) 2023-05-01 12:03:23 +02:00
Christian 4b7d035e9e Reset release notes 2023-04-29 12:37:36 +02:00
Christian cb3c44f48d Rename InspectPackage method (#1713) 2023-04-23 13:57:59 +02:00
Christian 8b3b9d33de Add overloads for options builder (#1711)
* Add new overload for options builder

* Refactor code

* Update ReleaseNotes.md
2023-04-11 22:11:16 +02:00
scottbrogden-iheartmediaandChristian 59d99509c8 Notify what subscription topics failed as part of the SynchronizingSu… (#1678)
* Notify what subscription topics failed as part of the SynchronizingSubscriptionsFailedAsync callback

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-04-08 14:03:58 +02:00
RossHNPCandChristian 2933b76237 ClientMessageQueueInterceptor continues with subscription processing. (#1685)
* ClientMessageQueueInterceptor continues with subscription processing.

* Update readme

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-04-06 17:55:33 +02:00
ChristianandMichi 36c6d8974e Allow disabling of packet fragmentation (#1709)
* Add compatibility option for AWS

* Fix UWP build

* Fix comment in MqttClientOptions (#1706)

* Refactor naming

* Fix wrong option value

---------

Co-authored-by: Michi <michael@volz.dev>
2023-04-06 17:54:36 +02:00
Christian d6cab76728 Add user properties to event args to interceptors for subscribing and unsubscribing (#1707)
Add user properties to event args
2023-04-06 17:31:42 +02:00
Christian b4f740f338 Update readme 2023-04-05 22:12:58 +02:00
Christian a004de25ac Fix unit test 2023-02-22 21:46:33 +01:00
老九 d0584413ca Add PayloadSegmentable feature (#1585) 2023-02-22 20:53:55 +01:00
Christian ba57537108 Fix running state handling in server (#1680)
Fix running state handling
2023-02-17 21:17:31 +01:00
Christian 6dcb110737 Add InterceptingClientEnqueueAsync event (#1679) 2023-02-17 19:13:06 +01:00
Christian b599a832da Add new method for reading a single retained message (#1676) 2023-02-15 19:46:37 +01:00
Christian 9cbd5dd778 Update ReleaseNotes.md 2023-01-28 13:08:24 +01:00
Christian ed31c51237 Code cleanup 2023-01-28 13:08:21 +01:00
Christian e01707d25e Improve samples 2023-01-28 13:08:08 +01:00
Jeffery MichaelandJeff Scherrer 73f118fe1c Moved the usage of certificate provider into the server listener (#1652)
* Moved the usage of certificate provider into the server listener so that each connection has the posibility of receiving a new SSL cert.

* Removed warning from unused field.

* Reduced changeds on original codebase.

---------

Co-authored-by: Jeff Scherrer <Jeff.Scherrer@vw.com>
2023-01-28 12:18:31 +01:00
Int32Overflow 0234918f30 Performance impact fix (#1661) 2023-01-28 12:12:20 +01:00
Jeffery Michael c90ba7e061 Fixed broken UWP build (#1655)
Added compiler declaritive to check for WINDOWS_UWP on IsAssignableFrom call.
2023-01-28 12:02:22 +01:00
128 changed files with 2471 additions and 2729 deletions
+13 -8
View File
@@ -1,8 +1,13 @@
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff). * [Core] Add validation of maximum string lengths (#1718).
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects. * [Core] Added .NET 4.8 builds (#1729).
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE). * [Core] Exposed more details of DISCONNECT packet in log (#1729).
* [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!). * [Core] Fixed _ArgumentNullException_ in _ConvertPayloadToString_ (#1745, thanks to @ramonsmits).
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event. * [Client] Added overloads for setting packet payload and will payload (#1720).
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord). * [Client] The proper connect result is now exposed in the _Disconnected_ event when authentication fails (#1139).
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544). * [Client] Exposed _UseDefaultCredentials_ and more for Web Socket options and proxy options (#1734, thanks to @impworks).
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638). * [Client] Exposed more TLS options (#1729).
* [Client] Fixed wrong return code conversion (#1729).
* [Client] Added an option to avoid throwing an exception when the server returned a proper non success (but valid) response (#1681).
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
* [Server] Fixed exceptions when clients are connecting and disconnecting very fast while accessing the client status for connection validation (#1742).
* [Server] Exposed more properties in _ClientConnectedEventArgs_ (#1738).
+1 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.2.0.${{github.run_number}}" VERSION: "4.2.1.${{github.run_number}}"
jobs: jobs:
build: build:
+1 -69
View File
@@ -1,67 +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"> <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_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_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
@@ -71,28 +8,23 @@
<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_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_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_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: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: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: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: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_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_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_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_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/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_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_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_ARGUMENTS_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_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</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_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: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: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_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: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/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
+16
View File
@@ -50,6 +50,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients * Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Getting Started
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
Use these command in the Package Manager console to install MQTTnet manually:
```
Install-Package MQTTnet
```
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
@@ -71,6 +71,32 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_With_Amazon_AWS()
{
/*
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
*
* The broker requires special settings which are set here.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important!
.WithoutPacketFragmentation()
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("The MQTT client is connected.");
await mqttClient.DisconnectAsync();
}
}
public static async Task Connect_Client_Timeout() public static async Task Connect_Client_Timeout()
{ {
/* /*
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
mqttClient.InspectPackage += OnInspectPackage; mqttClient.InspectPacketAsync += OnInspectPacket;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
} }
} }
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
{ {
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound) if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
{ {
@@ -44,9 +44,9 @@ public sealed class Managed_Client_Simple_Samples
Console.WriteLine("The managed MQTT client is connected."); Console.WriteLine("The managed MQTT client is connected.");
// Wait until the queue is fully processed. // Wait until the queue is fully processed.
SpinWait.SpinUntil(() => managedMqttClient.EnqueuedApplicationMessagesCount == 0, 10000); SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
Console.WriteLine($"Pending messages = {managedMqttClient.EnqueuedApplicationMessagesCount}"); Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
} }
} }
} }
+18 -1
View File
@@ -4,7 +4,13 @@
using System.Reflection; using System.Reflection;
Console.WriteLine("Welcome to MQTTnet samples!"); Console.BackgroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Red;
Console.Title = "Samples - MQTTnet";
Console.WriteLine("----------------------------------------------");
Console.WriteLine("- Welcome to MQTTnet samples! -");
Console.WriteLine("----------------------------------------------");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList(); var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample class (press Enter to continue): "); Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine(); var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0"); var selectedIndex = int.Parse(input ?? "0");
var selectedSampleClass = sampleClasses[selectedIndex]; var selectedSampleClass = sampleClasses[selectedIndex];
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample (press Enter to continue): "); Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor();
input = Console.ReadLine(); input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0"); selectedIndex = int.Parse(input ?? "0");
var selectedSampleMethod = sampleMethods[selectedIndex]; var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Executing sample..."); Console.WriteLine("Executing sample...");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
try try
@@ -17,7 +17,7 @@ using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
public static class Server_Asp_Net_Samples public static class Server_ASP_NET_Samples
{ {
public static Task Start_Server_With_WebSockets_Support() public static Task Start_Server_With_WebSockets_Support()
{ {
+75
View File
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
public static class Server_TLS_Samples
{
public static async Task Run_Server_With_Self_Signed_Certificate()
{
/*
* This sample starts a simple MQTT server which will accept any TCP connection.
* It also has an encrypted connection using a self signed TLS certificate.
*
* See sample "Run_Minimal_Server" for more details.
*/
var mqttFactory = new MqttFactory();
// This certificate is self signed so that
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
}
static X509Certificate2 CreateSelfSignedCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null!,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
}
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe; connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection); var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false); await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync(); var readResult = await pipe.Send.Reader.ReadAsync();
Assert.IsTrue(readResult.Buffer.Length > 20000); Assert.IsTrue(readResult.Buffer.Length > 20000);
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
{ {
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join(); var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count); var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
@@ -2,38 +2,34 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
using System; using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{ {
IMqttNetLogger _logger = new MqttNetNullLogger(); IMqttNetLogger _logger = MqttNetNullLogger.Instance;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; } public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public void Dispose()
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
{ {
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); if (webSocket == null)
{
throw new ArgumentNullException(nameof(webSocket));
}
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate); var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger)) using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
{ {
await clientHandler(channelAdapter).ConfigureAwait(false); await clientHandler(channelAdapter).ConfigureAwait(false);
} }
@@ -60,8 +56,15 @@ namespace MQTTnet.AspNetCore
} }
} }
public void Dispose() public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
} }
} }
@@ -73,7 +73,7 @@ namespace MQTTnet.Benchmarks
var channel = new MemoryMqttChannel(_stream); var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger()); _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
} }
static byte[] Join(params ArraySegment<byte>[] chunks) static byte[] Join(params ArraySegment<byte>[] chunks)
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
var writer = new MqttBufferWriter(4096, 65535); var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); _readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
} }
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589); writer.WriteVariableByteInteger(8574589);
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.Reset(0); writer.Reset(0);
} }
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
public void Deserialize_10000_Messages() public void Deserialize_10000_Messages()
{ {
var channel = new BenchmarkMqttChannel(_serializedPacket); var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger()); var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
@@ -17,10 +17,8 @@ namespace MQTTnet.Extensions.ManagedClient
public ManagedMqttApplicationMessage ApplicationMessage { get; } public ManagedMqttApplicationMessage ApplicationMessage { get; }
/// <summary> /// <summary>
/// Gets or sets the exception which was thrown while sending the message. /// Then this is _null_ the message was processed successfully without any error.
/// </summary> /// </summary>
public Exception Exception { get; } public Exception Exception { get; }
public bool HasFailed => Exception != null;
} }
} }
@@ -6,9 +6,9 @@ using System;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public sealed class ApplicationMessageDroppedEventArgs : EventArgs public sealed class ApplicationMessageSkippedEventArgs : EventArgs
{ {
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage) public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
{ {
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
} }
@@ -1,7 +1,3 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System; using System;
using MQTTnet.Client; using MQTTnet.Client;
@@ -16,7 +12,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
/// <summary> /// <summary>
/// This is null when the server was not reachable and thus no MQTT response was transmitted. /// This is null when the connection was failing and the server was not reachable.
/// </summary> /// </summary>
public MqttClientConnectResult ConnectResult { get; } public MqttClientConnectResult ConnectResult { get; }
@@ -1,35 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Packets;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public interface IManagedMqttClient : IDisposable public interface IManagedMqttClient : IDisposable
{ {
event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync;
event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync;
event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync; event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync;
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync; event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync; event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync; event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
event Func<EventArgs, Task> ConnectionStateChangedAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync; event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync; event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
int EnqueuedApplicationMessagesCount { get; }
IMqttClient InternalClient { get; } IMqttClient InternalClient { get; }
@@ -39,6 +33,8 @@ namespace MQTTnet.Extensions.ManagedClient
ManagedMqttClientOptions Options { get; } ManagedMqttClientOptions Options { get; }
int PendingApplicationMessagesCount { get; }
Task EnqueueAsync(MqttApplicationMessage applicationMessage); Task EnqueueAsync(MqttApplicationMessage applicationMessage);
Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage); Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage);
@@ -47,10 +43,10 @@ namespace MQTTnet.Extensions.ManagedClient
Task StartAsync(ManagedMqttClientOptions options); Task StartAsync(ManagedMqttClientOptions options);
Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken); Task StopAsync(bool cleanDisconnect = true);
Task SubscribeAsync(MqttClientSubscribeOptions options); Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
Task UnsubscribeAsync(MqttClientUnsubscribeOptions options); Task UnsubscribeAsync(ICollection<string> topics);
} }
} }
@@ -2,10 +2,9 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName> <AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace> <RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -1,4 +0,0 @@
<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,16 +6,10 @@ using System;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public sealed class ManagedMqttApplicationMessage public class ManagedMqttApplicationMessage
{ {
public ManagedMqttApplicationMessage(string id, MqttApplicationMessage applicationMessage) public Guid Id { get; set; } = Guid.NewGuid();
{
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,19 +6,15 @@ using System;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public sealed class ManagedMqttApplicationMessageBuilder public class ManagedMqttApplicationMessageBuilder
{ {
MqttApplicationMessage _applicationMessage; private Guid _id = Guid.NewGuid();
string _id = Guid.NewGuid().ToString(); private MqttApplicationMessage _applicationMessage;
public ManagedMqttApplicationMessage Build() public ManagedMqttApplicationMessageBuilder WithId(Guid id)
{ {
if (_applicationMessage == null) _id = id;
{ return this;
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
}
return new ManagedMqttApplicationMessage(_id, _applicationMessage);
} }
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage) public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
@@ -29,10 +25,7 @@ namespace MQTTnet.Extensions.ManagedClient
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder) public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder)
{ {
if (builder == null) if (builder == null) throw new ArgumentNullException(nameof(builder));
{
throw new ArgumentNullException(nameof(builder));
}
var internalBuilder = new MqttApplicationMessageBuilder(); var internalBuilder = new MqttApplicationMessageBuilder();
builder(internalBuilder); builder(internalBuilder);
@@ -41,16 +34,18 @@ namespace MQTTnet.Extensions.ManagedClient
return this; return this;
} }
public ManagedMqttApplicationMessageBuilder WithId(Guid id) public ManagedMqttApplicationMessage Build()
{ {
_id = id.ToString(); if (_applicationMessage == null)
return this; {
} throw new InvalidOperationException("The ApplicationMessage cannot be null.");
}
public ManagedMqttApplicationMessageBuilder WithId(string id) return new ManagedMqttApplicationMessage
{ {
_id = id ?? throw new ArgumentNullException(nameof(id)); Id = _id,
return this; ApplicationMessage = _applicationMessage
};
} }
} }
} }
@@ -17,10 +17,10 @@ using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public sealed class ManagedMqttClient : Disposable public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
{ {
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>(); readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
readonly AsyncEvent<ApplicationMessageDroppedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageDroppedEventArgs>(); readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>(); readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>(); readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>(); readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
@@ -76,7 +76,7 @@ namespace MQTTnet.Extensions.ManagedClient
_logger = logger.WithSource(nameof(ManagedMqttClient)); _logger = logger.WithSource(nameof(ManagedMqttClient));
} }
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageSkippedAsync public event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync
{ {
add => _applicationMessageSkippedEvent.AddHandler(value); add => _applicationMessageSkippedEvent.AddHandler(value);
remove => _applicationMessageSkippedEvent.RemoveHandler(value); remove => _applicationMessageSkippedEvent.RemoveHandler(value);
@@ -118,18 +118,12 @@ namespace MQTTnet.Extensions.ManagedClient
remove => InternalClient.DisconnectedAsync -= value; remove => InternalClient.DisconnectedAsync -= value;
} }
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync; public event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
public event Func<ManagedProcessFailedEventArgs, Task> SubscribedAsync
{ {
add => _synchronizingSubscriptionsFailedEvent.AddHandler(value); add => _synchronizingSubscriptionsFailedEvent.AddHandler(value);
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value); remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
} }
public event Func<EventArgs, Task> ApplicationMessageEnqueued;
public IMqttClient InternalClient { get; } public IMqttClient InternalClient { get; }
public bool IsConnected => InternalClient.IsConnected; public bool IsConnected => InternalClient.IsConnected;
@@ -170,7 +164,7 @@ namespace MQTTnet.Extensions.ManagedClient
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage); MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
ManagedMqttApplicationMessage removedMessage = null; ManagedMqttApplicationMessage removedMessage = null;
ApplicationMessageDroppedEventArgs applicationMessageDroppedEventArgs = null; ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
try try
{ {
@@ -181,7 +175,7 @@ namespace MQTTnet.Extensions.ManagedClient
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage) if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{ {
_logger.Verbose("Skipping publish of new application message because internal queue is full."); _logger.Verbose("Skipping publish of new application message because internal queue is full.");
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(applicationMessage); applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
return; return;
} }
@@ -189,7 +183,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
removedMessage = _messageQueue.RemoveFirst(); removedMessage = _messageQueue.RemoveFirst();
_logger.Verbose("Removed oldest application message from internal queue because it is full."); _logger.Verbose("Removed oldest application message from internal queue because it is full.");
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(removedMessage); applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
} }
} }
@@ -208,9 +202,9 @@ namespace MQTTnet.Extensions.ManagedClient
} }
finally finally
{ {
if (applicationMessageDroppedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers) if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
{ {
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageDroppedEventArgs).ConfigureAwait(false); await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
} }
} }
} }
@@ -359,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime; return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
} }
async Task HandleSubscriptionExceptionAsync(Exception exception) async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{ {
_logger.Warning(exception, "Synchronizing subscriptions failed."); _logger.Warning(exception, "Synchronizing subscriptions failed.");
if (_synchronizingSubscriptionsFailedEvent.HasHandlers) if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
{ {
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
} }
} }
@@ -463,11 +457,13 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
_logger.Info("Publishing subscriptions at reconnect"); _logger.Info("Publishing subscriptions at reconnect");
List<MqttTopicFilter> topicFilters = null;
try try
{ {
if (_reconnectSubscriptions.Any()) if (_reconnectSubscriptions.Any())
{ {
var topicFilters = new List<MqttTopicFilter>(); topicFilters = new List<MqttTopicFilter>();
foreach (var sub in _reconnectSubscriptions) foreach (var sub in _reconnectSubscriptions)
{ {
@@ -485,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
} }
} }
@@ -597,6 +593,9 @@ namespace MQTTnet.Extensions.ManagedClient
} }
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false); await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
} }
if (addedSubscriptions != null && addedSubscriptions.Any()) if (addedSubscriptions != null && addedSubscriptions.Any())
@@ -613,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
} }
} }
@@ -3,9 +3,8 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Threading; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -39,35 +38,6 @@ namespace MQTTnet.Extensions.ManagedClient
return managedMqttClient.EnqueueAsync(applicationMessage); 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( public static Task SubscribeAsync(
this IManagedMqttClient managedMqttClient, this IManagedMqttClient managedMqttClient,
string topic, string topic,
@@ -83,8 +53,11 @@ namespace MQTTnet.Extensions.ManagedClient
throw new ArgumentNullException(nameof(topic)); throw new ArgumentNullException(nameof(topic));
} }
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build(); return managedMqttClient.SubscribeAsync(
return managedMqttClient.SubscribeAsync(subscribeOptions); new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
});
} }
public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic) public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic)
@@ -99,8 +72,7 @@ namespace MQTTnet.Extensions.ManagedClient
throw new ArgumentNullException(nameof(topic)); throw new ArgumentNullException(nameof(topic));
} }
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build(); return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
return managedMqttClient.UnsubscribeAsync(unsubscribeOptions);
} }
} }
} }
@@ -10,15 +10,6 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
public sealed class ManagedMqttClientOptions public sealed class ManagedMqttClientOptions
{ {
public ManagedMqttClientOptions()
{
}
public ManagedMqttClientOptions(MqttClientOptions clientOptions)
{
ClientOptions = clientOptions;
}
public MqttClientOptions ClientOptions { get; set; } public MqttClientOptions ClientOptions { get; set; }
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
@@ -33,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
/// <summary> /// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet. /// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon AWS limits this number to 8. The default is int.MaxValue. /// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
/// </summary> /// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue; public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
} }
@@ -8,7 +8,7 @@ using MQTTnet.Server;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public sealed class ManagedMqttClientOptionsBuilder public class ManagedMqttClientOptionsBuilder
{ {
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions(); readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
MqttClientOptionsBuilder _clientOptionsBuilder; MqttClientOptionsBuilder _clientOptionsBuilder;
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedProcessFailedEventArgs : EventArgs
{
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
if (addedSubscriptions != null)
{
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
}
if (removedSubscriptions != null)
{
RemovedSubscriptions = new List<string>(removedSubscriptions);
}
}
public Exception Exception { get; }
public List<string> AddedSubscriptions { get; }
public List<string> RemovedSubscriptions { get; }
}
}
@@ -10,21 +10,6 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
public static class MqttFactoryExtensions public static class MqttFactoryExtensions
{ {
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient, IMqttNetLogger logger)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new NewManagedMqttClient(factory.CreateMqttClient(), logger);
}
return new NewManagedMqttClient(mqttClient, logger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null) public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{ {
if (factory == null) if (factory == null)
@@ -34,10 +19,10 @@ namespace MQTTnet.Extensions.ManagedClient
if (mqttClient == null) if (mqttClient == null)
{ {
return new NewManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger); return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
} }
return new NewManagedMqttClient(mqttClient, factory.DefaultLogger); return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
} }
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger) public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
@@ -52,10 +37,10 @@ namespace MQTTnet.Extensions.ManagedClient
throw new ArgumentNullException(nameof(logger)); throw new ArgumentNullException(nameof(logger));
} }
return new NewManagedMqttClient(factory.CreateMqttClient(logger), logger); return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
} }
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory factory) public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
{ {
return new ManagedMqttClientOptionsBuilder(); return new ManagedMqttClientOptionsBuilder();
} }
@@ -1,95 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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;
}
}
}
}
@@ -1,21 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
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; }
}
}
@@ -1,18 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.ManagedClient
{
public class ManagedProcessFailedEventArgs : EventArgs
{
public ManagedProcessFailedEventArgs(Exception exception)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
}
public Exception Exception { get; }
}
}
@@ -1,27 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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; }
}
}
@@ -1,27 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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; }
}
}
@@ -1,15 +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.
namespace MQTTnet.Extensions.ManagedClient
{
public enum MaintainConnectionResult
{
NoChange,
ConnectedEstablished,
NotConnected
}
}
@@ -1,35 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using 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));
}
}
}
@@ -1,155 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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);
}
}
}
}
@@ -1,103 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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);
}
}
}
@@ -1,309 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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!
}
}
}
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName> <AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
@@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
@@ -119,7 +120,6 @@ namespace MQTTnet.Extensions.Rpc
finally finally
{ {
_waitingCalls.TryRemove(responseTopic, out _); _waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false); await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
} }
} }
@@ -131,7 +131,8 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance; return CompletedTask.Instance;
} }
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload); var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc. // Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true; eventArgs.IsHandled = true;
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName> <AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter( return new MqttChannelAdapter(
new MqttTcpChannel(options), new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector, logger)
logger); {
PacketInspector = packetInspector
};
} }
case MqttClientWebSocketOptions webSocketOptions: case MqttClientWebSocketOptions webSocketOptions:
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter( return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions), new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector, logger)
logger); {
PacketInspector = packetInspector
};
} }
default: default:
+10 -1
View File
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
var payloadText = string.Empty;
if (e.ApplicationMessage.PayloadSegment.Count > 0)
{
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); Console.WriteLine($"+ Payload = {payloadText}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Console.WriteLine();
+4 -4
View File
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
return new MqttApplicationMessage return new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes(Payload), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
} }
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
}; };
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };
+11 -4
View File
@@ -5,6 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
{ {
// Replace the payload with the timestamp. But also extending a JSON // Replace the payload with the timestamp. But also extending a JSON
// based payload with the timestamp is a suitable use case. // based payload with the timestamp is a suitable use case.
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
} }
if (e.ApplicationMessage.Topic == "not_allowed_topic") if (e.ApplicationMessage.Topic == "not_allowed_topic")
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
{ {
MqttNetConsoleLogger.PrintToConsole( var payloadText = string.Empty;
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", if (e.ApplicationMessage.PayloadSegment.Count > 0)
ConsoleColor.Magenta); {
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -1,178 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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);
}
}
}
}
@@ -1,135 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.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);
}
}
}
}
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
@@ -19,39 +20,37 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Clients.ManagedMqttClient namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
[TestClass] [TestClass]
public sealed class ManagedMqttClient_Connection_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
{ {
[TestMethod] [TestMethod]
public async Task Connect_To_Invalid_Server() public async Task Expose_Custom_Connection_Error()
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
await LongTestDelay();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient()); server.ValidatingConnectionAsync += args =>
ConnectingFailedEventArgs connectingFailedEventArgs = null;
managedClient.ConnectingFailedAsync += args =>
{ {
connectingFailedEventArgs = args; args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
await managedClient.StartAsync( var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(2)).WithTcpServer("wrong_server", 1234).Build()
});
await managedClient.EnqueueAsync("test_topic_2"); MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
SpinWait.SpinUntil(() => connectingFailedEventArgs != null, 10000); var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
// The wrong server must be reported in general. await LongTestDelay();
Assert.IsNotNull(connectingFailedEventArgs);
Assert.IsNotNull(connectingFailedEventArgs.Exception); Assert.IsNotNull(disconnectedArgs);
Assert.IsNull(connectingFailedEventArgs.ConnectResult); Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
} }
} }
@@ -116,6 +115,115 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
} }
} }
[TestMethod]
public async Task Connect_To_Invalid_Server()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
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()
});
await managedClient.EnqueueAsync("test_topic_2");
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 Drop_New_Messages_On_Full_Queue()
{
var factory = new MqttFactory();
var managedClient = factory.CreateManagedMqttClient();
try
{
var clientOptions = new ManagedMqttClientOptionsBuilder().WithMaxPendingMessages(5)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
clientOptions.WithClientOptions(o => o.WithTcpServer("localhost"));
await managedClient.StartAsync(clientOptions.Build());
await managedClient.EnqueueAsync("1");
await managedClient.EnqueueAsync("2");
await managedClient.EnqueueAsync("3");
await managedClient.EnqueueAsync("4");
await managedClient.EnqueueAsync("5");
await managedClient.EnqueueAsync("6");
await managedClient.EnqueueAsync("7");
await managedClient.EnqueueAsync("8");
Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount);
}
finally
{
await managedClient.StopAsync();
}
}
[TestMethod]
public async Task ManagedClients_Will_Message_Send()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var receivedMessagesCount = 0;
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithWillTopic("My/last/will")
.WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
.Build();
var dyingClient = testEnvironment.CreateClient();
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
// Wait until the managed client is fully set up and running.
await Task.Delay(1000);
var receivingClient = await testEnvironment.ConnectClient();
receivingClient.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return CompletedTask.Instance;
};
await receivingClient.SubscribeAsync("My/last/will");
// Disposing the client will not sent a DISCONNECT packet so that connection is terminated
// which will lead to the will publish.
dyingManagedClient.Dispose();
// Wait for arrival of the will message at the receiver.
await Task.Delay(5000);
Assert.AreEqual(1, receivedMessagesCount);
}
}
[TestMethod] [TestMethod]
public async Task Start_Stop() public async Task Start_Stop()
{ {
@@ -123,18 +231,18 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient(); var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
await connected; await connected;
await managedClient.StopAsync(); await managedClient.StopAsync();
await LongTestDelay(); await Task.Delay(500);
Assert.AreEqual(0, (await server.GetClientsAsync()).Count); Assert.AreEqual(0, (await server.GetClientsAsync()).Count);
} }
@@ -150,7 +258,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var managedClient = testEnvironment.CreateManagedClient(); var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage(); var storage = new ManagedMqttClientTestStorage();
@@ -190,8 +298,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
await testEnvironment.StartServer();
var unmanagedClient = testEnvironment.CreateClient(); var unmanagedClient = testEnvironment.CreateClient();
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient); var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
@@ -208,7 +314,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.SubscribeAsync("unsubscribedThenSubscribed"); await managedClient.SubscribeAsync("unsubscribedThenSubscribed");
//wait a bit for the subscriptions to become established before the messages are published //wait a bit for the subscriptions to become established before the messages are published
await LongTestDelay(); await Task.Delay(500);
var sendingClient = await testEnvironment.ConnectClient(); var sendingClient = await testEnvironment.ConnectClient();
@@ -238,7 +344,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await connected; await connected;
// wait a bit so that the managed client can reestablish the subscriptions // wait a bit so that the managed client can reestablish the subscriptions
await LongTestDelay(); await Task.Delay(500);
received = SetupReceivingOfMessages(managedClient, 2); received = SetupReceivingOfMessages(managedClient, 2);
@@ -254,7 +360,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
var server = await testEnvironment.StartServer().ConfigureAwait(false); await testEnvironment.StartServer().ConfigureAwait(false);
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false); var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
await sendingClient.PublishStringAsync("topic", "A", retain: true); await sendingClient.PublishStringAsync("topic", "A", retain: true);
@@ -264,10 +370,10 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await sendingClient.DisconnectAsync(); 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. // 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 receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient()); var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
@@ -279,10 +385,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
var managedClientOptions = testEnvironment.CreateDefaultManagedMqttClientOptions(); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
managedClientOptions.ClientOptions.CleanSession = false;
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay(); await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count); Assert.AreEqual(1, receivedManagedMessages.Count);
@@ -290,8 +393,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.StopAsync(); await managedClient.StopAsync();
await LongTestDelay(); await LongTestDelay();
await managedClient.StartAsync(managedClientOptions); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
await LongTestDelay(); await LongTestDelay();
// After reconnect and then some delay, the retained message must not be received, // After reconnect and then some delay, the retained message must not be received,
@@ -313,13 +415,12 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
await testEnvironment.StartServer();
// Use a long connection check interval to verify that the subscriptions // Use a long connection check interval to verify that the subscriptions
// do not depend on the connection check interval anymore // do not depend on the connection check interval anymore
var connectionCheckInterval = TimeSpan.FromSeconds(10); var connectionCheckInterval = TimeSpan.FromSeconds(10);
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient(); var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var subscribeTime = DateTime.UtcNow; var subscribeTime = DateTime.UtcNow;
@@ -344,9 +445,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
await testEnvironment.StartServer(); var managedClient = await CreateManagedClientAsync(testEnvironment);
var managedClient = testEnvironment.CreateManagedClient();
var sendingClient = await testEnvironment.ConnectClient(); var sendingClient = await testEnvironment.ConnectClient();
@@ -372,52 +471,14 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
} }
} }
[TestMethod] async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
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, TestEnvironment testEnvironment,
IMqttClient underlyingClient = null, IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null, TimeSpan? connectionCheckInterval = null,
string host = "localhost") string host = "localhost")
{ {
await testEnvironment.StartServer();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer(host, testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer(host, testEnvironment.ServerPort);
var managedOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build(); var managedOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build();
@@ -427,7 +488,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
// at connection check intervals // at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1); managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient = testEnvironment.CreateManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient()); var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
@@ -438,20 +499,35 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
return managedClient; return managedClient;
} }
static Task GetConnectedTask(IManagedMqttClient managedClient) /// <summary>
/// Returns a task that will finish when the
/// <paramref
/// name="managedClient" />
/// has connected
/// </summary>
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
{ {
var promise = new TaskCompletionSource<bool>(); var connected = new TaskCompletionSource<bool>();
managedClient.ConnectedAsync += e => managedClient.ConnectedAsync += e =>
{ {
promise.TrySetResult(true); connected.TrySetResult(true);
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
return promise.Task; return connected.Task;
} }
static Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(IManagedMqttClient managedClient, int expectedNumberOfMessages) /// <summary>
/// Returns a task that will return the messages received on
/// <paramref
/// name="managedClient" />
/// when
/// <paramref
/// name="expectedNumberOfMessages" />
/// have been received
/// </summary>
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
{ {
var receivedMessages = new List<MqttApplicationMessage>(); var receivedMessages = new List<MqttApplicationMessage>();
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>(); var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -474,7 +550,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
{ {
IList<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>(); IList<ManagedMqttApplicationMessage> _messages;
public int GetMessageCount() public int GetMessageCount()
{ {
@@ -483,6 +559,11 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync() public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
{ {
if (_messages == null)
{
_messages = new List<ManagedMqttApplicationMessage>();
}
return Task.FromResult(_messages); return Task.FromResult(_messages);
} }
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -10,6 +11,7 @@ using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -99,6 +101,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect. // Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions); await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs); Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType); Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
} }
@@ -125,6 +129,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect. // Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions); await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs); Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode); Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
} }
@@ -151,6 +157,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect. // Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions); await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs); Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties); Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count); Assert.AreEqual(1, eventArgs.UserProperties.Count);
@@ -158,5 +166,35 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value); Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
} }
} }
[TestMethod]
public async Task Return_Non_Success()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ResponseUserProperties = new List<MqttUserProperty>
{
new MqttUserProperty("Property", "Value")
};
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
Assert.IsNotNull(response);
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
Assert.AreEqual(response.UserProperties[0].Name, "Property");
Assert.AreEqual(response.UserProperties[0].Value, "Value");
}
}
} }
} }
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
var packets = new List<string>(); var packets = new List<string>();
mqttClient.InspectPackage += eventArgs => mqttClient.InspectPacketAsync += eventArgs =>
{ {
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return CompletedTask.Instance; return CompletedTask.Instance;
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
var publishPacket = new MqttPublishPacket var publishPacket = new MqttPublishPacket
{ {
Topic = "abcdefghijklmnopqrstuvwxyz0123456789", Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
Payload = payload PayloadSegment = new ArraySegment<byte>(payload)
}; };
var serializationHelper = new MqttPacketSerializationHelper(); var serializationHelper = new MqttPacketSerializationHelper();
var buffer = serializationHelper.Encode(publishPacket); var buffer = serializationHelper.Encode(publishPacket);
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNotNull(publishPacketCopy); Assert.IsNotNull(publishPacketCopy);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
// Now modify the payload and test again. // Now modify the payload and test again.
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT"); publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
buffer = serializationHelper.Encode(publishPacket); buffer = serializationHelper.Encode(publishPacket);
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
Assert.IsNotNull(publishPacketCopy2); Assert.IsNotNull(publishPacketCopy2);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
} }
[TestMethod] [TestMethod]
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
using (var adapter = new MqttChannelAdapter( using (var adapter = new MqttChannelAdapter(
channel, channel,
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
null,
new MqttNetEventLogger())) new MqttNetEventLogger()))
{ {
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer) MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
{ {
var channel = new MemoryMqttChannel(buffer); var channel = new MemoryMqttChannel(buffer);
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
return packetFormatterAdapter.ProtocolVersion; return packetFormatterAdapter.ProtocolVersion;
} }
MqttBufferReader ReaderFactory(byte[] data)
{
var reader = new MqttBufferReader();
reader.SetBuffer(data, 0, data.Length);
return reader;
}
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
{ {
var writer = bufferWriter ?? WriterFactory(); var writer = bufferWriter ?? WriterFactory();
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray())) using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
{ {
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -76,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
} }
[TestMethod] [TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310() public void Serialize_Full_MqttConnAckPacket_V310()
{ {
var connAckPacket = new MqttConnAckPacket var connAckPacket = new MqttConnAckPacket
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
@@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter namespace MQTTnet.Tests.Formatter
@@ -48,22 +49,31 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(9876U, reader.ReadVariableByteInteger()); Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
} }
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Throw_If_String_Too_Long()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString(string.Empty.PadLeft(65536));
}
[TestMethod] [TestMethod]
public void Write_And_Read_Multiple_Times() public void Write_And_Read_Multiple_Times()
{ {
var writer = new MqttBufferWriter(4096, 65535); var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinaryData(new byte[1234]); writer.WriteBinary(new byte[1234]);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(new byte[48]); writer.WriteBinary(new byte[48]);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(new byte[3]); writer.WriteBinary(new byte[3]);
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
public void AssertReceivedCountEquals(int expectedCount) public void AssertReceivedCountEquals(int expectedCount)
{ {
lock (_receivedEventArgs) Assert.AreEqual(expectedCount, Count);
{
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
}
} }
public string GeneratePayloadSequence() public string GeneratePayloadSequence()
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#region
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -21,8 +19,6 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
#endregion
namespace MQTTnet.Tests.Mockups namespace MQTTnet.Tests.Mockups
{ {
public sealed class TestEnvironment : IDisposable public sealed class TestEnvironment : IDisposable
@@ -32,7 +28,6 @@ namespace MQTTnet.Tests.Mockups
readonly List<Exception> _exceptions = new List<Exception>(); readonly List<Exception> _exceptions = new List<Exception>();
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>(); readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly List<IManagedMqttClient> _managedClients = new List<IManagedMqttClient>();
readonly MqttProtocolVersion _protocolVersion; readonly MqttProtocolVersion _protocolVersion;
readonly List<string> _serverErrors = new List<string>(); readonly List<string> _serverErrors = new List<string>();
@@ -94,12 +89,8 @@ namespace MQTTnet.Tests.Mockups
public int ServerPort { get; set; } public int ServerPort { get; set; }
public List<MqttApplicationMessage> ServerReceivedApplicationMessages { get; } = new List<MqttApplicationMessage>();
public TestContext TestContext { get; } public TestContext TestContext { get; }
public bool TrackServerReceivedApplicationMessages { get; set; }
public Task<IMqttClient> ConnectClient() public Task<IMqttClient> ConnectClient()
{ {
return ConnectClient(Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion)); return ConnectClient(Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion));
@@ -247,11 +238,6 @@ namespace MQTTnet.Tests.Mockups
} }
} }
public ManagedMqttClientOptions CreateDefaultManagedMqttClientOptions()
{
return Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(Factory.CreateClientOptionsBuilder().WithTcpServer("127.0.0.1", ServerPort).Build()).Build();
}
public MqttClientOptions CreateDefaultClientOptions() public MqttClientOptions CreateDefaultClientOptions()
{ {
return CreateDefaultClientOptionsBuilder().Build(); return CreateDefaultClientOptionsBuilder().Build();
@@ -259,7 +245,7 @@ namespace MQTTnet.Tests.Mockups
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder() public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{ {
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort); return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
} }
public ILowLevelMqttClient CreateLowLevelClient() public ILowLevelMqttClient CreateLowLevelClient()
@@ -273,21 +259,6 @@ 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) public MqttServer CreateServer(MqttServerOptions options)
{ {
if (Server != null) if (Server != null)
@@ -299,11 +270,6 @@ namespace MQTTnet.Tests.Mockups
Server = Factory.CreateMqttServer(options, logger); Server = Factory.CreateMqttServer(options, logger);
if (TrackServerReceivedApplicationMessages)
{
Server.InterceptingPublishAsync += ServerOnInterceptingPublishAsync;
}
Server.ValidatingConnectionAsync += e => Server.ValidatingConnectionAsync += e =>
{ {
if (TestContext != null) if (TestContext != null)
@@ -340,18 +306,6 @@ namespace MQTTnet.Tests.Mockups
} }
} }
foreach (var managedMqttClient in _managedClients)
{
try
{
managedMqttClient.StopAsync().Wait();
}
finally
{
managedMqttClient.Dispose();
}
}
foreach (var lowLevelMqttClient in _lowLevelClients) foreach (var lowLevelMqttClient in _lowLevelClients)
{ {
lowLevelMqttClient.Dispose(); lowLevelMqttClient.Dispose();
@@ -378,13 +332,6 @@ namespace MQTTnet.Tests.Mockups
} }
} }
public async Task<IManagedMqttClient> StartManagedClient()
{
var managedClient = Factory.CreateManagedMqttClient(CreateClient());
await managedClient.StartAsync(CreateDefaultManagedMqttClientOptions());
return managedClient;
}
public Task<MqttServer> StartServer() public Task<MqttServer> StartServer()
{ {
return StartServer(Factory.CreateServerOptionsBuilder()); return StartServer(Factory.CreateServerOptionsBuilder());
@@ -454,15 +401,5 @@ namespace MQTTnet.Tests.Mockups
_exceptions.Add(exception); _exceptions.Add(exception);
} }
} }
Task ServerOnInterceptingPublishAsync(InterceptingPublishEventArgs arg)
{
lock (ServerReceivedApplicationMessages)
{
ServerReceivedApplicationMessages.Add(arg.ApplicationMessage);
}
return CompletedTask.Instance;
}
} }
} }
@@ -0,0 +1,48 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttApplicationMessageTest
{
[TestMethod]
public void PayloadSegment()
{
var message = new MqttApplicationMessage();
Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsFalse(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
}
}
}
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
{ {
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535)); var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance); var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
writer.WriteString("1234567890"); writer.WriteString("1234567890");
Assert.AreEqual(10 + 2, writer.Length); Assert.AreEqual(10 + 2, writer.Length);
writer.WriteBinaryData(new byte[300]); writer.WriteBinary(new byte[300]);
Assert.AreEqual(300 + 2 + 12, writer.Length); Assert.AreEqual(300 + 2 + 12, writer.Length);
writer.WriteBinaryData(new byte[5000]); writer.WriteBinary(new byte[5000]);
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length); Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
} }
} }
+1 -1
View File
@@ -954,7 +954,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient(); var client = testEnvironment.CreateClient();
client.InspectPackage += e => client.InspectPacketAsync += e =>
{ {
if (e.Buffer.Length > 0) if (e.Buffer.Length > 0)
{ {
@@ -228,7 +228,7 @@ namespace MQTTnet.Tests.Server
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _); MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId); return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
} }
} }
@@ -1,58 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Publish_Tests : BaseTestClass
{
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
}
}
@@ -5,6 +5,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -27,5 +29,118 @@ namespace MQTTnet.Tests.Server
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
} }
} }
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Intercept_Client_Enqueue()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
server.InterceptingClientEnqueueAsync += e =>
{
e.AcceptEnqueue = false;
return CompletedTask.Instance;
};
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
// Do not increase because the internal enqueue to the target client is not accepted!
receivedMessages.AssertReceivedCountEquals(1);
}
}
[TestMethod]
public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One"));
await receiverOne.SubscribeAsync("A");
var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne);
var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two"));
await receiverTwo.SubscribeAsync("A");
var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo);
var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three"));
await receiverThree.SubscribeAsync("A");
var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree);
server.InterceptingClientEnqueueAsync += e =>
{
if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false;
return CompletedTask.Instance;
};
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
receivedMessagesOne.AssertReceivedCountEquals(1);
receivedMessagesTwo.AssertReceivedCountEquals(0);
receivedMessagesThree.AssertReceivedCountEquals(1);
}
}
} }
} }
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Server
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build(); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions)); var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode); Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
} }
} }
@@ -90,6 +90,29 @@ namespace MQTTnet.Tests.Server
} }
} }
[TestMethod]
public async Task Intercept_Subscribe_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
InterceptingSubscriptionEventArgs eventArgs = null;
server.InterceptingSubscriptionAsync += e =>
{
eventArgs = e;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
await client.SubscribeAsync(subscribeOptions);
CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
}
}
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))] [ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Subscribing() public async Task Disconnect_While_Subscribing()
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
var topics = t.Value; var topics = t.Value;
foreach (var topic in topics) foreach (var topic in topics)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
bucketDepths.TryGetValue(topicHash, out var currentValue); bucketDepths.TryGetValue(topicHash, out var currentValue);
++currentValue; ++currentValue;
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
{ {
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#"; var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
} }
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
{ {
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+"; var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
} }
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
// UInt64 is limited to 8 levels // UInt64 is limited to 8 levels
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString(); var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present"); Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString(); var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
var l3 = "#"; var l3 = "#";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1"; var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present"); Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1"; var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask) void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Topic: " + topic); Console.WriteLine("Topic: " + topic);
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
{ {
var topicToFind = topicsToFind[countDown]; var topicToFind = topicsToFind[countDown];
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _); MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result); clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
if (result.IsSubscribed) if (result.IsSubscribed)
+204
View File
@@ -0,0 +1,204 @@
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
using System;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Certificates;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Tls_Tests : BaseTestClass
{
static X509Certificate2 CreateCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
[TestMethod]
public async Task Tls_Swap_Test()
{
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
var firstOid = "1.3.6.1.5.5.7.3.1";
var secondOid = "1.3.6.1.5.5.7.3.2";
var certificateProvider = new CertificateProvider
{
CurrentCertificate = CreateCertificate(firstOid)
};
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
var serverOptions = serverOptionsBuilder.Build();
var server = testEnvironment.CreateServer(serverOptions);
var publishedCount = 0;
server.InterceptingPublishAsync += args =>
{
Interlocked.Increment(ref publishedCount);
return Task.CompletedTask;
};
await server.StartAsync();
var firstClient = await ConnectClientAsync(
testEnvironment,
args =>
{
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
return true;
});
var firstClientReceivedCount = 0;
firstClient.ApplicationMessageReceivedAsync += args =>
{
Interlocked.Increment(ref firstClientReceivedCount);
return Task.CompletedTask;
};
await firstClient.SubscribeAsync("TestTopic1");
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
}));
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
// Validate that the certificate was switched
var secondClient = await ConnectClientAsync(
testEnvironment,
args =>
{
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
return true;
});
var secondClientReceivedCount = 0;
secondClient.ApplicationMessageReceivedAsync += args =>
{
Interlocked.Increment(ref secondClientReceivedCount);
return Task.CompletedTask;
};
await secondClient.SubscribeAsync("TestTopic2");
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic2",
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic2",
Payload = new byte[] { 1, 2, 3, 4 }
}));
// Ensure first client still works
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
}));
await Task.Delay(1000);
Assert.AreEqual(6, publishedCount);
Assert.AreEqual(4, firstClientReceivedCount);
Assert.AreEqual(2, secondClientReceivedCount);
await server.StopAsync();
}
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
{
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer("localhost", 8883)
.WithTls(
tls =>
{
tls.UseTls = true;
tls.SslProtocol = SslProtocols.Tls12;
tls.CertificateValidationHandler = certValidator;
});
var clientOptions = clientOptionsBuilder.Build();
return await testEnvironment.ConnectClient(clientOptions);
}
sealed class CertificateProvider : ICertificateProvider
{
public X509Certificate2 CurrentCertificate { get; set; }
public X509Certificate2 GetCertificate()
{
return CurrentCertificate;
}
}
}
}
#endif
@@ -2,10 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
{ {
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
await client.UnsubscribeAsync("#"); await client.UnsubscribeAsync("#");
} }
} }
[TestMethod]
public async Task Intercept_Unsubscribe_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
InterceptingUnsubscriptionEventArgs eventArgs = null;
server.InterceptingUnsubscriptionAsync += e =>
{
eventArgs = e;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var unsubscribeOptions = testEnvironment.Factory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
await client.UnsubscribeAsync(unsubscribeOptions);
CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
}
}
} }
} }
@@ -150,18 +150,8 @@ namespace MQTTnet.Tests
{ {
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter)); Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
ulong topicHash; MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
ulong topicHashMask; MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
bool topicHasWildcard;
MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
ulong filterTopicHash;
ulong filterTopicHashMask;
bool filterTopicHasWildcard;
MqttSubscription.CalculateTopicHash(filter, out filterTopicHash, out filterTopicHashMask, out filterTopicHasWildcard);
if (expectedResult == MqttTopicFilterCompareResult.IsMatch) if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
{ {
+21 -17
View File
@@ -27,18 +27,15 @@ namespace MQTTnet.Adapter
readonly byte[] _fixedHeaderBuffer = new byte[2]; readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly MqttPacketInspector _packetInspector;
readonly byte[] _singleByteBuffer = new byte[1]; readonly byte[] _singleByteBuffer = new byte[1];
readonly AsyncLock _syncRoot = new AsyncLock(); readonly AsyncLock _syncRoot = new AsyncLock();
Statistics _statistics; // mutable struct, don't make readonly! Statistics _statistics; // mutable struct, don't make readonly!
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger) public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger)
{ {
_channel = channel ?? throw new ArgumentNullException(nameof(channel)); _channel = channel ?? throw new ArgumentNullException(nameof(channel));
_packetInspector = packetInspector;
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
@@ -64,6 +61,10 @@ namespace MQTTnet.Adapter
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public MqttPacketInspector PacketInspector { get; set; }
public bool AllowPacketFragmentation { get; set; } = true;
public async Task ConnectAsync(CancellationToken cancellationToken) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -126,7 +127,7 @@ namespace MQTTnet.Adapter
try try
{ {
_packetInspector?.BeginReceivePacket(); PacketInspector?.BeginReceivePacket();
ReceivedMqttPacket receivedPacket; ReceivedMqttPacket receivedPacket;
var receivedPacketTask = ReceiveAsync(cancellationToken); var receivedPacketTask = ReceiveAsync(cancellationToken);
@@ -144,7 +145,7 @@ namespace MQTTnet.Adapter
return null; return null;
} }
_packetInspector?.EndReceivePacket(); PacketInspector?.EndReceivePacket();
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength); Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
@@ -180,7 +181,10 @@ namespace MQTTnet.Adapter
return null; return null;
} }
public void ResetStatistics() => _statistics.Reset(); public void ResetStatistics()
{
_statistics.Reset();
}
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{ {
@@ -197,18 +201,18 @@ namespace MQTTnet.Adapter
try try
{ {
var packetBuffer = PacketFormatterAdapter.Encode(packet); var packetBuffer = PacketFormatterAdapter.Encode(packet);
_packetInspector?.BeginSendPacket(packetBuffer); PacketInspector?.BeginSendPacket(packetBuffer);
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
if (packetBuffer.Payload.Count > 0) if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
{ {
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
} }
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length); Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
@@ -270,7 +274,7 @@ namespace MQTTnet.Adapter
return 0; return 0;
} }
_packetInspector?.FillReceiveBuffer(_singleByteBuffer); PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
encodedByte = _singleByteBuffer[0]; encodedByte = _singleByteBuffer[0];
@@ -323,7 +327,7 @@ namespace MQTTnet.Adapter
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
_packetInspector?.FillReceiveBuffer(buffer); PacketInspector?.FillReceiveBuffer(buffer);
var hasRemainingLength = buffer[1] != 0; var hasRemainingLength = buffer[1] != 0;
if (!hasRemainingLength) if (!hasRemainingLength)
@@ -407,7 +411,7 @@ namespace MQTTnet.Adapter
bodyOffset += readBytes; bodyOffset += readBytes;
} while (bodyOffset < bodyLength); } while (bodyOffset < bodyLength);
_packetInspector?.FillReceiveBuffer(body); PacketInspector?.FillReceiveBuffer(body);
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength); var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength); return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
@@ -455,7 +459,7 @@ namespace MQTTnet.Adapter
throw new MqttCommunicationException(exception); throw new MqttCommunicationException(exception);
} }
private struct Statistics struct Statistics
{ {
public long _bytesReceived; public long _bytesReceived;
public long _bytesSent; public long _bytesSent;
@@ -11,112 +11,110 @@ namespace MQTTnet.Client
public sealed class MqttClientConnectResult public sealed class MqttClientConnectResult
{ {
/// <summary> /// <summary>
/// Gets the client identifier which was chosen by the server. /// Gets the result code.
/// MQTTv5 only. /// MQTTv5 only.
/// </summary>
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets a value indicating whether a session was already available or not.
/// MQTTv5 only.
/// </summary>
public bool IsSessionPresent { get; internal set; }
/// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
/// MQTTv5 only.
/// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets whether the server supports retained messages.
/// MQTTv5 only.
/// </summary>
public bool RetainAvailable { get; internal set; }
/// <summary>
/// Gets the client identifier which was chosen by the server.
/// MQTTv5 only.
/// </summary> /// </summary>
public string AssignedClientIdentifier { get; internal set; } public string AssignedClientIdentifier { get; internal set; }
/// <summary> /// <summary>
/// Gets the authentication data. /// Gets the authentication method.
/// MQTTv5 only. /// MQTTv5 only.
/// </summary>
public byte[] AuthenticationData { get; internal set; }
/// <summary>
/// Gets the authentication method.
/// MQTTv5 only.
/// </summary> /// </summary>
public string AuthenticationMethod { get; internal set; } public string AuthenticationMethod { get; internal set; }
/// <summary> /// <summary>
/// Gets a value indicating whether a session was already available or not. /// Gets the authentication data.
/// MQTTv5 only. /// MQTTv5 only.
/// </summary> /// </summary>
public bool IsSessionPresent { get; internal set; } public byte[] AuthenticationData { get; internal set; }
public uint? MaximumPacketSize { get; internal set; } public uint? MaximumPacketSize { get; internal set; }
/// <summary> /// <summary>
/// Gets the maximum QoS which is supported by the server. /// Gets the reason string.
/// MQTTv5 only. /// MQTTv5 only.
/// </summary>
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
/// <summary>
/// Gets the reason string.
/// MQTTv5 only.
/// </summary> /// </summary>
public string ReasonString { get; internal set; } public string ReasonString { get; internal set; }
public ushort? ReceiveMaximum { get; internal set; } public ushort? ReceiveMaximum { get; internal set; }
/// <summary> /// <summary>
/// Gets the response information. /// Gets the maximum QoS which is supported by the server.
/// MQTTv5 only. /// MQTTv5 only.
/// </summary>
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
/// <summary>
/// Gets the response information.
/// MQTTv5 only.
/// </summary> /// </summary>
public string ResponseInformation { get; internal set; } public string ResponseInformation { get; internal set; }
/// <summary> /// <summary>
/// Gets the result code. /// Gets the maximum value for a topic alias. 0 means not supported.
/// MQTTv5 only. /// MQTTv5 only.
/// </summary>
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets whether the server supports retained messages.
/// MQTTv5 only.
/// </summary>
public bool RetainAvailable { get; internal set; }
/// <summary>
/// MQTTv5 only.
/// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// A value of 0 indicates that the feature is not used.
/// </summary>
public ushort ServerKeepAlive { get; internal set; }
/// <summary>
/// Gets an alternate server which should be used instead of the current one.
/// MQTTv5 only.
/// </summary>
public string ServerReference { get; internal set; }
public uint? SessionExpiryInterval { get; internal set; }
/// <summary>
/// Gets a value indicating whether the shared subscriptions are available or not.
/// MQTTv5 only.
/// </summary>
public bool SharedSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets a value indicating whether the subscription identifiers are available or not.
/// MQTTv5 only.
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets the maximum value for a topic alias. 0 means not supported.
/// MQTTv5 only.
/// </summary> /// </summary>
public ushort TopicAliasMaximum { get; internal set; } public ushort TopicAliasMaximum { get; internal set; }
/// <summary> /// <summary>
/// Gets the user properties. /// Gets an alternate server which should be used instead of the current one.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT /// MQTTv5 only.
/// packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// MQTTv5 only.
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; internal set; } public string ServerReference { get; internal set; }
/// <summary> /// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server. /// MQTTv5 only.
/// MQTTv5 only. /// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// A value of 0 indicates that the feature is not used.
/// </summary> /// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; } public ushort ServerKeepAlive { get; internal set; }
public uint? SessionExpiryInterval { get; internal set; }
/// <summary>
/// Gets a value indicating whether the subscription identifiers are available or not.
/// MQTTv5 only.
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets a value indicating whether the shared subscriptions are available or not.
/// MQTTv5 only.
/// </summary>
public bool SharedSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// MQTTv5 only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; internal set; }
} }
} }
@@ -9,14 +9,9 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectOptions public sealed class MqttClientDisconnectOptions
{ {
/// <summary>
/// Gets or sets whether this disconnect is a clean disconnect.
/// </summary>
public bool IsClean { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reason code. /// Gets or sets the reason code.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection; public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
+1 -1
View File
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync; event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPackage; event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
bool IsConnected { get; } bool IsConnected { get; }
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Client.Internal
{
public sealed class MqttClientEvents
{
public AsyncEvent<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceivedEvent { get; } = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
public AsyncEvent<MqttClientConnectingEventArgs> ConnectingEvent { get; } = new AsyncEvent<MqttClientConnectingEventArgs>();
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
public AsyncEvent<InspectMqttPacketEventArgs> InspectPacketEvent { get; } = new AsyncEvent<InspectMqttPacketEventArgs>();
}
}
@@ -0,0 +1,14 @@
// 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.Client.Internal
{
public static class MqttClientResultFactory
{
public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory();
public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory();
public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory();
public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory();
}
}
+200 -177
View File
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client.Internal;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -20,17 +21,9 @@ namespace MQTTnet.Client
public sealed class MqttClient : Disposable, IMqttClient public sealed class MqttClient : Disposable, IMqttClient
{ {
readonly IMqttClientAdapterFactory _adapterFactory; readonly IMqttClientAdapterFactory _adapterFactory;
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
readonly MqttClientSubscribeResultFactory _clientSubscribeResultFactory = new MqttClientSubscribeResultFactory();
readonly MqttClientUnsubscribeResultFactory _clientUnsubscribeResultFactory = new MqttClientUnsubscribeResultFactory();
readonly AsyncEvent<MqttClientConnectedEventArgs> _connectedEvent = new AsyncEvent<MqttClientConnectedEventArgs>();
readonly AsyncEvent<MqttClientConnectingEventArgs> _connectingEvent = new AsyncEvent<MqttClientConnectingEventArgs>();
readonly AsyncEvent<MqttClientDisconnectedEventArgs> _disconnectedEvent = new AsyncEvent<MqttClientDisconnectedEventArgs>();
readonly object _disconnectLock = new object(); readonly object _disconnectLock = new object();
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>(); readonly MqttClientEvents _events = new MqttClientEvents();
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
@@ -65,35 +58,35 @@ namespace MQTTnet.Client
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{ {
add => _applicationMessageReceivedEvent.AddHandler(value); add => _events.ApplicationMessageReceivedEvent.AddHandler(value);
remove => _applicationMessageReceivedEvent.RemoveHandler(value); remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value);
} }
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{ {
add => _connectedEvent.AddHandler(value); add => _events.ConnectedEvent.AddHandler(value);
remove => _connectedEvent.RemoveHandler(value); remove => _events.ConnectedEvent.RemoveHandler(value);
} }
public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync
{ {
add => _connectingEvent.AddHandler(value); add => _events.ConnectingEvent.AddHandler(value);
remove => _connectingEvent.RemoveHandler(value); remove => _events.ConnectingEvent.RemoveHandler(value);
} }
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
{ {
add => _disconnectedEvent.AddHandler(value); add => _events.DisconnectedEvent.AddHandler(value);
remove => _disconnectedEvent.RemoveHandler(value); remove => _events.DisconnectedEvent.RemoveHandler(value);
} }
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
{ {
add => _inspectPacketEvent.AddHandler(value); add => _events.InspectPacketEvent.AddHandler(value);
remove => _inspectPacketEvent.RemoveHandler(value); remove => _events.InspectPacketEvent.RemoveHandler(value);
} }
public bool IsConnected => _adapter != null && (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected; public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
public MqttClientOptions Options { get; private set; } public MqttClientOptions Options { get; private set; }
@@ -114,9 +107,9 @@ namespace MQTTnet.Client
{ {
Options = options; Options = options;
if (_connectingEvent.HasHandlers) if (_events.ConnectingEvent.HasHandlers)
{ {
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options)).ConfigureAwait(false); await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
} }
Cleanup(); Cleanup();
@@ -127,12 +120,12 @@ namespace MQTTnet.Client
_mqttClientAlive = new CancellationTokenSource(); _mqttClientAlive = new CancellationTokenSource();
var mqttClientAliveToken = _mqttClientAlive.Token; var mqttClientAliveToken = _mqttClientAlive.Token;
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger); var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
_adapter = adapter; _adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter.");
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false); connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
@@ -140,46 +133,50 @@ namespace MQTTnet.Client
// CancellationToken.None or similar. // CancellationToken.None or similar.
using (var timeout = new CancellationTokenSource(Options.Timeout)) using (var timeout = new CancellationTokenSource(Options.Timeout))
{ {
connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false); connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false);
} }
} }
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
{
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
return connectResult;
}
_lastPacketSentTimestamp = DateTime.UtcNow; _lastPacketSentTimestamp = DateTime.UtcNow;
var keepAliveInterval = Options.KeepAlivePeriod; var keepAliveInterval = Options.KeepAlivePeriod;
if (connectResult.ServerKeepAlive > 0) if (connectResult.ServerKeepAlive > 0)
{ {
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server."); _logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server");
keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive); keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
} }
if (keepAliveInterval != TimeSpan.Zero) if (keepAliveInterval != TimeSpan.Zero)
{ {
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(mqttClientAliveToken), mqttClientAliveToken); _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(mqttClientAliveToken), mqttClientAliveToken);
} }
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting); CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
_logger.Info("Connected."); _logger.Info("Connected");
if (_connectedEvent.HasHandlers) await OnConnected(connectResult).ConfigureAwait(false);
{
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
await _connectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
return connectResult; return connectResult;
} }
catch (Exception exception) catch (Exception exception)
{ {
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError; if (exception is MqttConnectingFailedException connectingFailedException)
if (!(exception is OperationCanceledException))
{ {
_logger.Error(exception, "Error while connecting with server."); connectResult = connectingFailedException.Result;
} }
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false); _disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
_logger.Error(exception, "Error while connecting with server");
await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false);
throw; throw;
} }
@@ -222,19 +219,19 @@ namespace MQTTnet.Client
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false); await Send(disconnectPacket, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
using (var timeout = new CancellationTokenSource(Options.Timeout)) using (var timeout = new CancellationTokenSource(Options.Timeout))
{ {
await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false); await Send(disconnectPacket, timeout.Token).ConfigureAwait(false);
} }
} }
} }
finally finally
{ {
await DisconnectCoreAsync(null, null, null, clientWasConnected).ConfigureAwait(false); await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false);
} }
} }
@@ -242,13 +239,13 @@ namespace MQTTnet.Client
{ {
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false); await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
using (var timeout = new CancellationTokenSource(Options.Timeout)) using (var timeout = new CancellationTokenSource(Options.Timeout))
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false); await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
} }
} }
} }
@@ -277,11 +274,11 @@ namespace MQTTnet.Client
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
{ {
return PublishAtLeastOnceAsync(publishPacket, cancellationToken); return PublishAtLeastOnce(publishPacket, cancellationToken);
} }
case MqttQualityOfServiceLevel.ExactlyOnce: case MqttQualityOfServiceLevel.ExactlyOnce:
{ {
return PublishExactlyOnceAsync(publishPacket, cancellationToken); return PublishExactlyOnce(publishPacket, cancellationToken);
} }
default: default:
{ {
@@ -309,7 +306,7 @@ namespace MQTTnet.Client
UserProperties = data.UserProperties UserProperties = data.UserProperties
}; };
return SendAsync(authPacket, cancellationToken); return Send(authPacket, cancellationToken);
} }
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default) public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
@@ -338,17 +335,17 @@ namespace MQTTnet.Client
MqttSubAckPacket subAckPacket; MqttSubAckPacket subAckPacket;
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false); subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
using (var timeout = new CancellationTokenSource(Options.Timeout)) using (var timeout = new CancellationTokenSource(Options.Timeout))
{ {
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false); subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
} }
} }
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket); return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket);
} }
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default) public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
@@ -377,17 +374,17 @@ namespace MQTTnet.Client
MqttUnsubAckPacket unsubAckPacket; MqttUnsubAckPacket unsubAckPacket;
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false); unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
using (var timeout = new CancellationTokenSource(Options.Timeout)) using (var timeout = new CancellationTokenSource(Options.Timeout))
{ {
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false); unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
} }
} }
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket); return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -411,7 +408,7 @@ namespace MQTTnet.Client
if (!eventArgs.ProcessingFailed) if (!eventArgs.ProcessingFailed)
{ {
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs); var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
return SendAsync(pubAckPacket, cancellationToken); return Send(pubAckPacket, cancellationToken);
} }
} }
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
@@ -419,7 +416,7 @@ namespace MQTTnet.Client
if (!eventArgs.ProcessingFailed) if (!eventArgs.ProcessingFailed)
{ {
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs); var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken); return Send(pubRecPacket, cancellationToken);
} }
} }
else else
@@ -430,27 +427,43 @@ namespace MQTTnet.Client
return CompletedTask.Instance; return CompletedTask.Instance;
} }
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken) async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
{ {
MqttClientConnectResult result; MqttClientConnectResult result;
try try
{ {
var connectPacket = MqttPacketFactories.Connect.Create(options); var connectPacket = MqttPacketFactories.Connect.Create(options);
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false); var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
var clientConnectResultFactory = new MqttClientConnectResultFactory(); if (receivedPacket is MqttConnAckPacket connAckPacket)
result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion); {
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
else
{
throw new NotSupportedException("Extended authentication handler is not yet supported");
}
} }
catch (Exception exception) catch (Exception exception)
{ {
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null); throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
} }
if (result.ResultCode != MqttClientConnectResultCode.Success) // This is no feature. It is basically a backward compatibility option and should be removed in the future.
// The client should not throw any exception if the transport layer connection was successful and the server
// did send a proper ACK packet with a non success response.
if (options.ThrowOnNonSuccessfulConnectResponse)
{ {
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result); _logger.Warning(
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
if (result.ResultCode != MqttClientConnectResultCode.Success)
{
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
}
} }
_logger.Verbose("Authenticated MQTT connection with server established."); _logger.Verbose("Authenticated MQTT connection with server established.");
@@ -485,27 +498,31 @@ namespace MQTTnet.Client
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand); return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
} }
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken) async Task<MqttClientConnectResult> ConnectInternal(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{ {
var backgroundCancellationToken = _mqttClientAlive.Token; var backgroundCancellationToken = _mqttClientAlive.Token;
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
{ {
_logger.Verbose("Trying to connect with server '{0}'.", Options.ChannelOptions); _logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions);
await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false); await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
_logger.Verbose("Connection with server established."); _logger.Verbose("Connection with server established");
_publishPacketReceiverQueue?.Dispose(); _publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>(); _publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken); var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
{
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
}
return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false); return connectResult;
} }
} }
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
{ {
TryInitiateDisconnect(); TryInitiateDisconnect();
@@ -560,17 +577,17 @@ namespace MQTTnet.Client
// This handler must be executed in a new thread because otherwise a dead lock may happen // This handler must be executed in a new thread because otherwise a dead lock may happen
// when trying to reconnect in that handler etc. // when trying to reconnect in that handler etc.
Task.Run(() => _disconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger); Task.Run(() => _events.DisconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
} }
} }
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult) Task DisconnectInternal(Task sender, Exception exception, MqttClientConnectResult connectResult)
{ {
var clientWasConnected = IsConnected; var clientWasConnected = IsConnected;
if (!DisconnectIsPendingOrFinished()) if (!DisconnectIsPendingOrFinished())
{ {
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected); return DisconnectCore(sender, exception, connectResult, clientWasConnected);
} }
return CompletedTask.Instance; return CompletedTask.Instance;
@@ -600,8 +617,7 @@ namespace MQTTnet.Client
connectionStatus = curStatus; connectionStatus = curStatus;
break; break;
} }
} } while (true);
while (true);
} }
void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket) void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket)
@@ -616,15 +632,26 @@ namespace MQTTnet.Client
} }
} }
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket) async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessage(MqttPublishPacket publishPacket)
{ {
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket); var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs; return eventArgs;
} }
Task OnConnected(MqttClientConnectResult connectResult)
{
if (_events.ConnectedEvent.HasHandlers)
{
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
return _events.ConnectedEvent.InvokeAsync(eventArgs);
}
return CompletedTask.Instance;
}
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket) Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
{ {
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler; var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
@@ -645,7 +672,7 @@ namespace MQTTnet.Client
// Also dispatch disconnect to waiting threads to generate a proper exception. // Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket)); _packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
return DisconnectInternalAsync(_packetReceiverTask, null, null); return DisconnectInternal(_packetReceiverTask, null, null);
} }
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
@@ -661,7 +688,7 @@ namespace MQTTnet.Client
} }
var publishPacket = publishPacketDequeueResult.Item; var publishPacket = publishPacketDequeueResult.Item;
var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false); var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false);
if (eventArgs.AutoAcknowledge) if (eventArgs.AutoAcknowledge)
{ {
@@ -685,7 +712,7 @@ namespace MQTTnet.Client
// The packet is unknown. Probably due to a restart of the client. // The packet is unknown. Probably due to a restart of the client.
// So wen send this to the server to trigger a full resend of the message. // So wen send this to the server to trigger a full resend of the message.
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound); var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
return SendAsync(pubRelPacket, cancellationToken); return Send(pubRelPacket, cancellationToken);
} }
return CompletedTask.Instance; return CompletedTask.Instance;
@@ -694,42 +721,117 @@ namespace MQTTnet.Client
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{ {
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken); return Send(pubCompPacket, cancellationToken);
} }
async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) async Task<MqttClientPublishResult> PublishAtLeastOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false); var pubAckPacket = await Request<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubAckPacket); return MqttClientResultFactory.PublishResult.Create(pubAckPacket);
} }
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken) async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false); await Send(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(null); return MqttClientResultFactory.PublishResult.Create(null);
} }
async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) async Task<MqttClientPublishResult> PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false); var pubRecPacket = await Request<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false); var pubCompPacket = await Request<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubRecPacket, pubCompPacket); return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket);
} }
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket async Task<MqttPacket> Receive(CancellationToken cancellationToken)
{
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
MqttPacket packet;
if (packetTask.IsCompleted)
{
packet = packetTask.Result;
}
else
{
packet = await packetTask.ConfigureAwait(false);
}
return packet;
}
async Task ReceivePacketsLoop(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start receiving packets.");
while (!cancellationToken.IsCancellationRequested)
{
var packet = await Receive(cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false);
return;
}
await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is AggregateException aggregateException)
{
exception = aggregateException.GetBaseException();
}
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
}
_packetDispatcher.FailAll(exception);
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
}
}
async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ThrowIfNoAdapter();
ushort packetIdentifier = 0; ushort packetIdentifier = 0;
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier) if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
@@ -741,7 +843,7 @@ namespace MQTTnet.Client
{ {
try try
{ {
await SendAsync(requestPacket, cancellationToken).ConfigureAwait(false); await Send(requestPacket, cancellationToken).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -765,12 +867,12 @@ namespace MQTTnet.Client
} }
} }
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken) Task Send(MqttPacket packet, CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ThrowIfNoAdapter();
_lastPacketSentTimestamp = DateTime.UtcNow; _lastPacketSentTimestamp = DateTime.UtcNow;
return _adapter.SendPacketAsync(packet, cancellationToken); return _adapter.SendPacketAsync(packet, cancellationToken);
} }
@@ -782,16 +884,6 @@ namespace MQTTnet.Client
} }
} }
void ThrowIfNoAdapter()
{
// Having no adapter indicates that the connection is closed or still being created.
// Thus the client is disconnected.
if (_adapter == null)
{
ThrowNotConnected();
}
}
void ThrowIfNotConnected() void ThrowIfNotConnected()
{ {
if (!IsConnected) if (!IsConnected)
@@ -838,7 +930,7 @@ namespace MQTTnet.Client
} }
} }
async Task TryProcessReceivedPacketAsync(MqttPacket packet, CancellationToken cancellationToken) async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken)
{ {
try try
{ {
@@ -899,80 +991,11 @@ namespace MQTTnet.Client
_packetDispatcher.FailAll(exception); _packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false); await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
} }
} }
async Task TryReceivePacketsAsync(CancellationToken cancellationToken) async Task TrySendKeepAliveMessages(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start receiving packets.");
while (!cancellationToken.IsCancellationRequested)
{
MqttPacket packet;
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
if (packetTask.IsCompleted)
{
packet = packetTask.Result;
}
else
{
packet = await packetTask.ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
await DisconnectInternalAsync(_packetReceiverTask, null, null).ConfigureAwait(false);
return;
}
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is AggregateException aggregateException)
{
exception = aggregateException.GetBaseException();
}
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
}
_packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
}
}
async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{ {
try try
{ {
@@ -1021,7 +1044,7 @@ namespace MQTTnet.Client
_logger.Error(exception, "Error exception while sending/receiving keep alive packets."); _logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
} }
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false); await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
} }
finally finally
{ {
@@ -12,6 +12,14 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientOptions public sealed class MqttClientOptions
{ {
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _false_.
/// </summary>
public bool AllowPacketFragmentation { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the authentication data. /// Gets or sets the authentication data.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
@@ -92,6 +100,11 @@ namespace MQTTnet.Client
/// </summary> /// </summary>
public uint SessionExpiryInterval { get; set; } public uint SessionExpiryInterval { get; set; }
/// <summary>
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
/// </summary>
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the timeout which will be applied at socket level and internal operations. /// Gets or sets the timeout which will be applied at socket level and internal operations.
/// The default value is the same as for sockets in .NET in general. /// The default value is the same as for sockets in .NET in general.
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
@@ -79,6 +79,16 @@ namespace MQTTnet.Client
return _options; return _options;
} }
/// <summary>
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
/// This will become the default behavior in future versions of the library.
/// </summary>
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
{
_options.ThrowOnNonSuccessfulConnectResponse = false;
return this;
}
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data) public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
{ {
_options.AuthenticationMethod = method; _options.AuthenticationMethod = method;
@@ -86,12 +96,24 @@ namespace MQTTnet.Client
return this; return this;
} }
/// <summary>
/// Clean session is used in MQTT versions below 5.0.0. It is the same as setting "CleanStart".
/// </summary>
public MqttClientOptionsBuilder WithCleanSession(bool value = true) public MqttClientOptionsBuilder WithCleanSession(bool value = true)
{ {
_options.CleanSession = value; _options.CleanSession = value;
return this; return this;
} }
/// <summary>
/// Clean start is used in MQTT versions 5.0.0 and higher. It is the same as setting "CleanSession".
/// </summary>
public MqttClientOptionsBuilder WithCleanStart(bool value = true)
{
_options.CleanSession = value;
return this;
}
public MqttClientOptionsBuilder WithClientId(string value) public MqttClientOptionsBuilder WithClientId(string value)
{ {
_options.ClientId = value; _options.ClientId = value;
@@ -188,6 +210,18 @@ namespace MQTTnet.Client
return WithKeepAlivePeriod(TimeSpan.Zero); return WithKeepAlivePeriod(TimeSpan.Zero);
} }
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _true_.
/// </summary>
public MqttClientOptionsBuilder WithoutPacketFragmentation()
{
_options.AllowPacketFragmentation = false;
return this;
}
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{ {
if (value == MqttProtocolVersion.Unknown) if (value == MqttProtocolVersion.Unknown)
@@ -373,6 +407,18 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
{
_options.WillContentType = willContentType;
return this;
}
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
{
_options.WillCorrelationData = willCorrelationData;
return this;
}
public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval) public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval)
{ {
_options.WillDelayInterval = willDelayInterval; _options.WillDelayInterval = willDelayInterval;
@@ -385,6 +431,18 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithWillPayload(ArraySegment<byte> willPayload)
{
if (willPayload.Count == 0)
{
_options.WillPayload = null;
return this;
}
_options.WillPayload = willPayload.ToArray();
return this;
}
public MqttClientOptionsBuilder WithWillPayload(string willPayload) public MqttClientOptionsBuilder WithWillPayload(string willPayload)
{ {
if (string.IsNullOrEmpty(willPayload)) if (string.IsNullOrEmpty(willPayload))
@@ -396,45 +454,33 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
{
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
return this;
}
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel) public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
{ {
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel; _options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
return this; return this;
} }
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
{
_options.WillTopic = willTopic;
return this;
}
public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
{
_options.WillRetain = willRetain;
return this;
}
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
{
_options.WillContentType = willContentType;
return this;
}
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
{
_options.WillCorrelationData = willCorrelationData;
return this;
}
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic) public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
{ {
_options.WillResponseTopic = willResponseTopic; _options.WillResponseTopic = willResponseTopic;
return this; return this;
} }
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator) public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
{ {
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator; _options.WillRetain = willRetain;
return this;
}
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
{
_options.WillTopic = willTopic;
return this; return this;
} }
@@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Security;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -30,11 +31,21 @@ namespace MQTTnet.Client
#endif #endif
#if NETCOREAPP3_1_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; } public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; } public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption;
public bool AllowRenegotiation { get; set; } = true;
#endif #endif
/// <summary>
/// Gets or sets the target host.
/// If the value is null or empty the same host as the TCP socket host will be used.
/// </summary>
public string TargetHost { get; set; }
#if NET48 || NETCOREAPP3_1_OR_GREATER #if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else #else
@@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.WebSockets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
@@ -11,6 +13,8 @@ namespace MQTTnet.Client
{ {
public CookieContainer CookieContainer { get; set; } public CookieContainer CookieContainer { get; set; }
public ICredentials Credentials { get; set; }
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; } public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
public IDictionary<string, string> RequestHeaders { get; set; } public IDictionary<string, string> RequestHeaders { get; set; }
@@ -25,5 +29,27 @@ namespace MQTTnet.Client
{ {
return Uri; return Uri;
} }
#if !NETSTANDARD1_3
#if !WINDOWS_UWP
/// <summary>
/// Gets or sets the keep alive interval for the Web Socket connection.
/// This is not related to the keep alive interval for the MQTT protocol.
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval;
/// <summary>
/// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection.
/// This is not related to the credentials which are used for the MQTT protocol.
/// </summary>
public bool UseDefaultCredentials { get; set; }
#else
/// <summary>
/// Gets or sets the keep alive interval for the Web Socket connection.
/// This is not related to the keep alive interval for the MQTT protocol.
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(30);
#endif
#endif
} }
} }
@@ -16,6 +16,8 @@ namespace MQTTnet.Client
public bool BypassOnLocal { get; set; } public bool BypassOnLocal { get; set; }
public bool UseDefaultCredentials { get; set; }
public string[] BypassList { get; set; } public string[] BypassList { get; set; }
} }
} }
@@ -10,11 +10,7 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientSubscribeResult public sealed class MqttClientSubscribeResult
{ {
public MqttClientSubscribeResult( public MqttClientSubscribeResult(ushort packetIdentifier, IReadOnlyCollection<MqttClientSubscribeResultItem> items, string reasonString, IReadOnlyCollection<MqttUserProperty> userProperties)
ushort packetIdentifier,
IReadOnlyCollection<MqttClientSubscribeResultItem> items,
string reasonString,
IReadOnlyCollection<MqttUserProperty> userProperties)
{ {
PacketIdentifier = packetIdentifier; PacketIdentifier = packetIdentifier;
Items = items ?? throw new ArgumentNullException(nameof(items)); Items = items ?? throw new ArgumentNullException(nameof(items));
@@ -23,26 +19,25 @@ namespace MQTTnet.Client
} }
/// <summary> /// <summary>
/// Gets the result for every topic filter item. /// Gets the result for every topic filter item.
/// </summary> /// </summary>
public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; } public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; }
/// <summary> /// <summary>
/// Gets the user properties which were part of the SUBACK packet. /// Gets the user properties which were part of the SUBACK packet.
/// MQTTv5 only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public ushort PacketIdentifier { get; } public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
/// <summary> /// <summary>
/// Gets the reason string. /// Gets the reason string.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; } public string ReasonString { get; }
/// <summary> /// <summary>
/// Gets the user properties which were part of the SUBACK packet. /// Gets the packet identifier which was used.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; } public ushort PacketIdentifier { get; }
} }
} }
@@ -42,6 +42,6 @@ namespace MQTTnet.Client
/// Gets the user properties which were part of the UNSUBACK packet. /// Gets the user properties which were part of the UNSUBACK packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; } public IReadOnlyCollection<MqttUserProperty> UserProperties { get; set; }
} }
} }
@@ -102,13 +102,6 @@ namespace MQTTnet.Diagnostics
logger.Publish(logLevel, message, new object[] { parameter1, parameter2 }, exception); logger.Publish(logLevel, message, new object[] { parameter1, parameter2 }, exception);
} }
public static MqttNetSourceLogger WithSource<TSource>(this IMqttNetLogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));
return new MqttNetSourceLogger(logger, typeof(TSource).Name);
}
public static void Verbose<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1) public static void Verbose<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
{ {
if (!logger.IsEnabled) if (!logger.IsEnabled)
@@ -16,6 +16,8 @@ namespace MQTTnet.Diagnostics
return "net461"; return "net461";
#elif NET472 #elif NET472
return "net472"; return "net472";
#elif NET48
return "net48";
#elif NETSTANDARD1_3 #elif NETSTANDARD1_3
return "netstandard1.3"; return "netstandard1.3";
#elif NETSTANDARD2_0 #elif NETSTANDARD2_0
@@ -19,7 +19,7 @@ namespace MQTTnet.Formatter
return new MqttApplicationMessage return new MqttApplicationMessage
{ {
Topic = publishPacket.Topic, Topic = publishPacket.Topic,
Payload = publishPacket.Payload, PayloadSegment = publishPacket.PayloadSegment,
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
Retain = publishPacket.Retain, Retain = publishPacket.Retain,
Dup = publishPacket.Dup, Dup = publishPacket.Dup,
+31 -22
View File
@@ -19,7 +19,8 @@ namespace MQTTnet.Formatter
/// </summary> /// </summary>
public sealed class MqttBufferWriter public sealed class MqttBufferWriter
{ {
public const uint VariableByteIntegerMaxValue = 268435455; const uint VariableByteIntegerMaxValue = 268435455;
const int EncodedStringMaxLength = 65535;
readonly int _maxBufferSize; readonly int _maxBufferSize;
@@ -112,25 +113,7 @@ namespace MQTTnet.Formatter
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length); WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
} }
public void WriteBinary(byte[] buffer, int offset, int count) public void WriteBinary(byte[] value)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (count == 0)
{
return;
}
EnsureAdditionalCapacity(count);
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count);
}
public void WriteBinaryData(byte[] value)
{ {
if (value == null || value.Length == 0) if (value == null || value.Length == 0)
{ {
@@ -155,6 +138,24 @@ namespace MQTTnet.Formatter
} }
} }
public void WriteBinary(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (count == 0)
{
return;
}
EnsureAdditionalCapacity(count);
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count);
}
public void WriteByte(byte @byte) public void WriteByte(byte @byte)
{ {
EnsureAdditionalCapacity(1); EnsureAdditionalCapacity(1);
@@ -186,6 +187,13 @@ namespace MQTTnet.Formatter
var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2); var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2);
// From RFC: 1.5.4 UTF-8 Encoded String
// Unless stated otherwise all UTF-8 encoded strings can have any length in the range 0 to 65,535 bytes.
if (writtenBytes > EncodedStringMaxLength)
{
throw new MqttProtocolViolationException($"The maximum string length is 65535. The current string has a length of {writtenBytes}.");
}
_buffer[_position] = (byte)(writtenBytes >> 8); _buffer[_position] = (byte)(writtenBytes >> 8);
_buffer[_position + 1] = (byte)writtenBytes; _buffer[_position + 1] = (byte)writtenBytes;
@@ -273,8 +281,7 @@ namespace MQTTnet.Formatter
newBufferLength *= 2; newBufferLength *= 2;
} }
// Array.Resize will create a new array and copy the existing // Array.Resize will create a new array and copy the existing data to the new one.
// data to the new one.
Array.Resize(ref _buffer, newBufferLength); Array.Resize(ref _buffer, newBufferLength);
} }
@@ -285,6 +292,8 @@ namespace MQTTnet.Formatter
if (_position > Length) if (_position > Length)
{ {
// Also extend the position because we reached the end of the
// pre allocated buffer.
Length = _position; Length = _position;
} }
} }
@@ -2,54 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using MQTTnet.Exceptions;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public static class MqttConnectReasonCodeConverter public static class MqttConnectReasonCodeConverter
{ {
public static MqttConnectReasonCode ToConnectReasonCode(MqttConnectReturnCode returnCode)
{
switch (returnCode)
{
case MqttConnectReturnCode.ConnectionAccepted:
{
return MqttConnectReasonCode.Success;
}
case MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion:
{
return MqttConnectReasonCode.UnsupportedProtocolVersion;
}
case MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword:
{
return MqttConnectReasonCode.BadUserNameOrPassword;
}
case MqttConnectReturnCode.ConnectionRefusedIdentifierRejected:
{
return MqttConnectReasonCode.ClientIdentifierNotValid;
}
case MqttConnectReturnCode.ConnectionRefusedServerUnavailable:
{
return MqttConnectReasonCode.ServerUnavailable;
}
case MqttConnectReturnCode.ConnectionRefusedNotAuthorized:
{
return MqttConnectReasonCode.NotAuthorized;
}
default:
{
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
}
}
}
public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode) public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode)
{ {
switch (reasonCode) switch (reasonCode)
@@ -59,11 +17,13 @@ namespace MQTTnet.Formatter
return MqttConnectReturnCode.ConnectionAccepted; return MqttConnectReturnCode.ConnectionAccepted;
} }
case MqttConnectReasonCode.Banned:
case MqttConnectReasonCode.NotAuthorized: case MqttConnectReasonCode.NotAuthorized:
{ {
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized; return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
} }
case MqttConnectReasonCode.BadAuthenticationMethod:
case MqttConnectReasonCode.BadUserNameOrPassword: case MqttConnectReasonCode.BadUserNameOrPassword:
{ {
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
@@ -79,6 +39,7 @@ namespace MQTTnet.Formatter
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion; return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
} }
case MqttConnectReasonCode.UseAnotherServer:
case MqttConnectReasonCode.ServerUnavailable: case MqttConnectReasonCode.ServerUnavailable:
case MqttConnectReasonCode.ServerBusy: case MqttConnectReasonCode.ServerBusy:
case MqttConnectReasonCode.ServerMoved: case MqttConnectReasonCode.ServerMoved:
@@ -87,9 +48,7 @@ namespace MQTTnet.Formatter
} }
default: default:
{ return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
}
} }
} }
} }
@@ -31,10 +31,14 @@ namespace MQTTnet.Formatter
public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown; public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown;
public void Cleanup()
{
_bufferWriter.Cleanup();
}
public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket) public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket)
{ {
ThrowIfFormatterNotSet(); ThrowIfFormatterNotSet();
return _formatter.Decode(receivedMqttPacket); return _formatter.Decode(receivedMqttPacket);
} }
@@ -50,11 +54,6 @@ namespace MQTTnet.Formatter
return _formatter.Encode(packet); return _formatter.Encode(packet);
} }
public void Cleanup()
{
_bufferWriter.Cleanup();
}
public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter) public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter)
{ {
if (protocolVersion == MqttProtocolVersion.Unknown) if (protocolVersion == MqttProtocolVersion.Unknown)
@@ -16,7 +16,7 @@ namespace MQTTnet.Formatter
return new MqttPublishPacket return new MqttPublishPacket
{ {
Topic = publishPacket.Topic, Topic = publishPacket.Topic,
Payload = publishPacket.Payload, PayloadSegment = publishPacket.PayloadSegment,
Retain = publishPacket.Retain, Retain = publishPacket.Retain,
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
Dup = publishPacket.Dup, Dup = publishPacket.Dup,
@@ -36,7 +36,7 @@ namespace MQTTnet.Formatter
var packet = new MqttPublishPacket var packet = new MqttPublishPacket
{ {
Topic = applicationMessage.Topic, Topic = applicationMessage.Topic,
Payload = applicationMessage.Payload, PayloadSegment = applicationMessage.PayloadSegment,
QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel,
Retain = applicationMessage.Retain, Retain = applicationMessage.Retain,
Dup = applicationMessage.Dup, Dup = applicationMessage.Dup,
@@ -65,10 +65,16 @@ namespace MQTTnet.Formatter
throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag)."); throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag).");
} }
ArraySegment<byte> willMessageBuffer = default;
if (connectPacket.WillMessage?.Length > 0)
{
willMessageBuffer = new ArraySegment<byte>(connectPacket.WillMessage);
}
var packet = new MqttPublishPacket var packet = new MqttPublishPacket
{ {
Topic = connectPacket.WillTopic, Topic = connectPacket.WillTopic,
Payload = connectPacket.WillMessage, PayloadSegment = willMessageBuffer,
QualityOfServiceLevel = connectPacket.WillQoS, QualityOfServiceLevel = connectPacket.WillQoS,
Retain = connectPacket.WillRetain, Retain = connectPacket.WillRetain,
ContentType = connectPacket.WillContentType, ContentType = connectPacket.WillContentType,
@@ -103,9 +103,10 @@ namespace MQTTnet.Formatter.V3
var remainingLength = (uint)(_bufferWriter.Length - 5); var remainingLength = (uint)(_bufferWriter.Length - 5);
var publishPacket = packet as MqttPublishPacket; var publishPacket = packet as MqttPublishPacket;
if (publishPacket?.Payload != null) var payloadSegment = publishPacket?.PayloadSegment;
if (payloadSegment != null)
{ {
remainingLength += (uint)publishPacket.Payload.Length; remainingLength += (uint)payloadSegment.Value.Count;
} }
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
@@ -119,16 +120,11 @@ namespace MQTTnet.Formatter.V3
_bufferWriter.WriteVariableByteInteger(remainingLength); _bufferWriter.WriteVariableByteInteger(remainingLength);
var buffer = _bufferWriter.GetBuffer(); var buffer = _bufferWriter.GetBuffer();
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset); var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
if (publishPacket?.Payload != null) return payloadSegment == null
{ ? new MqttPacketBuffer(firstSegment)
var payloadSegment = new ArraySegment<byte>(publishPacket.Payload, 0, publishPacket.Payload.Length); : new MqttPacketBuffer(firstSegment, payloadSegment.Value);
return new MqttPacketBuffer(firstSegment, payloadSegment);
}
return new MqttPacketBuffer(firstSegment);
} }
MqttPacket DecodeConnAckPacket(ArraySegment<byte> body) MqttPacket DecodeConnAckPacket(ArraySegment<byte> body)
@@ -282,7 +278,7 @@ namespace MQTTnet.Formatter.V3
if (!_bufferReader.EndOfStream) if (!_bufferReader.EndOfStream)
{ {
packet.Payload = _bufferReader.ReadRemainingData(); packet.PayloadSegment = new ArraySegment<byte>(_bufferReader.ReadRemainingData());
} }
return packet; return packet;
@@ -463,7 +459,7 @@ namespace MQTTnet.Formatter.V3
if (packet.WillFlag) if (packet.WillFlag)
{ {
bufferWriter.WriteString(packet.WillTopic); bufferWriter.WriteString(packet.WillTopic);
bufferWriter.WriteBinaryData(packet.WillMessage); bufferWriter.WriteBinary(packet.WillMessage);
} }
if (packet.Username != null) if (packet.Username != null)
@@ -473,7 +469,7 @@ namespace MQTTnet.Formatter.V3
if (packet.Password != null) if (packet.Password != null)
{ {
bufferWriter.WriteBinaryData(packet.Password); bufferWriter.WriteBinary(packet.Password);
} }
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
@@ -525,7 +521,7 @@ namespace MQTTnet.Formatter.V3
if (packet.WillFlag) if (packet.WillFlag)
{ {
bufferWriter.WriteString(packet.WillTopic); bufferWriter.WriteString(packet.WillTopic);
bufferWriter.WriteBinaryData(packet.WillMessage); bufferWriter.WriteBinary(packet.WillMessage);
} }
if (packet.Username != null) if (packet.Username != null)
@@ -535,7 +531,7 @@ namespace MQTTnet.Formatter.V3
if (packet.Password != null) if (packet.Password != null)
{ {
bufferWriter.WriteBinaryData(packet.Password); bufferWriter.WriteBinary(packet.Password);
} }
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
@@ -130,6 +130,9 @@ namespace MQTTnet.Formatter.V5
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce
}; };
// Also set the return code of MQTT 3.1.1 for backward compatibility and debugging purposes.
packet.ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(packet.ReasonCode);
var propertiesReader = new MqttV5PropertiesReader(_bufferReader); var propertiesReader = new MqttV5PropertiesReader(_bufferReader);
while (propertiesReader.MoveNext()) while (propertiesReader.MoveNext())
{ {
@@ -528,7 +531,7 @@ namespace MQTTnet.Formatter.V5
if (!_bufferReader.EndOfStream) if (!_bufferReader.EndOfStream)
{ {
packet.Payload = _bufferReader.ReadRemainingData(); packet.PayloadSegment = new ArraySegment<byte>(_bufferReader.ReadRemainingData());
} }
return packet; return packet;
@@ -37,9 +37,11 @@ namespace MQTTnet.Formatter.V5
var remainingLength = (uint)_bufferWriter.Length - 5; var remainingLength = (uint)_bufferWriter.Length - 5;
var publishPacket = packet as MqttPublishPacket; var publishPacket = packet as MqttPublishPacket;
if (publishPacket?.Payload != null) var payloadSegment = publishPacket?.PayloadSegment;
if (payloadSegment != null)
{ {
remainingLength += (uint)publishPacket.Payload.Length; remainingLength += (uint)payloadSegment.Value.Count;
} }
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
@@ -53,16 +55,11 @@ namespace MQTTnet.Formatter.V5
_bufferWriter.WriteVariableByteInteger(remainingLength); _bufferWriter.WriteVariableByteInteger(remainingLength);
var buffer = _bufferWriter.GetBuffer(); var buffer = _bufferWriter.GetBuffer();
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset); var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
if (publishPacket?.Payload != null) return payloadSegment == null
{ ? new MqttPacketBuffer(firstSegment)
var payloadSegment = new ArraySegment<byte>(publishPacket.Payload, 0, publishPacket.Payload.Length); : new MqttPacketBuffer(firstSegment, payloadSegment.Value);
return new MqttPacketBuffer(firstSegment, payloadSegment);
}
return new MqttPacketBuffer(firstSegment);
} }
byte EncodeAuthPacket(MqttAuthPacket packet) byte EncodeAuthPacket(MqttAuthPacket packet)
@@ -189,7 +186,7 @@ namespace MQTTnet.Formatter.V5
_propertiesWriter.Reset(); _propertiesWriter.Reset();
_bufferWriter.WriteString(packet.WillTopic); _bufferWriter.WriteString(packet.WillTopic);
_bufferWriter.WriteBinaryData(packet.WillMessage); _bufferWriter.WriteBinary(packet.WillMessage);
} }
if (packet.Username != null) if (packet.Username != null)
@@ -199,7 +196,7 @@ namespace MQTTnet.Formatter.V5
if (packet.Password != null) if (packet.Password != null)
{ {
_bufferWriter.WriteBinaryData(packet.Password); _bufferWriter.WriteBinary(packet.Password);
} }
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
@@ -335,7 +335,7 @@ namespace MQTTnet.Formatter.V5
} }
_bufferWriter.WriteByte((byte)id); _bufferWriter.WriteByte((byte)id);
_bufferWriter.WriteBinaryData(value); _bufferWriter.WriteBinary(value);
} }
void WriteAsFourByteInteger(MqttPropertyId id, uint value) void WriteAsFourByteInteger(MqttPropertyId id, uint value)
@@ -40,7 +40,12 @@ namespace MQTTnet.Implementations
var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax); var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax);
var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter); var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter);
return new MqttChannelAdapter(channel, packetFormatterAdapter, packetInspector, logger);
return new MqttChannelAdapter(channel, packetFormatterAdapter, logger)
{
AllowPacketFragmentation = options.AllowPacketFragmentation,
PacketInspector = packetInspector
};
} }
} }
} }
@@ -100,6 +100,12 @@ namespace MQTTnet.Implementations
if (_tcpOptions.TlsOptions?.UseTls == true) if (_tcpOptions.TlsOptions?.UseTls == true)
{ {
var targetHost = _tcpOptions.TlsOptions.TargetHost;
if (string.IsNullOrEmpty(targetHost))
{
targetHost = _tcpOptions.Server;
}
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback); var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
try try
{ {
@@ -111,14 +117,16 @@ namespace MQTTnet.Implementations
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol, EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
CertificateRevocationCheckMode = CertificateRevocationCheckMode =
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode, _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
TargetHost = _tcpOptions.Server, TargetHost = targetHost,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy,
EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy,
AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation
}; };
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
#else #else
await sslStream.AuthenticateAsClientAsync( await sslStream.AuthenticateAsClientAsync(
_tcpOptions.Server, targetHost,
LoadCertificates(), LoadCertificates(),
_tcpOptions.TlsOptions.SslProtocol, _tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors) !_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
@@ -290,12 +298,12 @@ namespace MQTTnet.Implementations
X509CertificateCollection LoadCertificates() X509CertificateCollection LoadCertificates()
{ {
var certificates = new X509CertificateCollection();
if (_tcpOptions.TlsOptions.Certificates == null) if (_tcpOptions.TlsOptions.Certificates == null)
{ {
return certificates; return null;
} }
var certificates = new X509CertificateCollection();
foreach (var certificate in _tcpOptions.TlsOptions.Certificates) foreach (var certificate in _tcpOptions.TlsOptions.Certificates)
{ {
certificates.Add(certificate); certificates.Add(certificate);
@@ -95,7 +95,7 @@ namespace MQTTnet.Implementations
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter); var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
var tcpChannel = new MqttTcpChannel(args.Socket, clientCertificate, _options); var tcpChannel = new MqttTcpChannel(args.Socket, clientCertificate, _options);
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, null, _rootLogger)) using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
{ {
await clientHandler(clientAdapter).ConfigureAwait(false); await clientHandler(clientAdapter).ConfigureAwait(false);
} }
@@ -1,19 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#if !WINDOWS_UWP #if !WINDOWS_UWP
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Server;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
@@ -21,16 +19,25 @@ namespace MQTTnet.Implementations
{ {
readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>(); readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>();
MqttServerOptions _serverOptions;
CancellationTokenSource _cancellationTokenSource; CancellationTokenSource _cancellationTokenSource;
MqttServerOptions _serverOptions;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; } public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public bool TreatSocketOpeningErrorAsWarning { get; set; } public bool TreatSocketOpeningErrorAsWarning { get; set; }
public void Dispose()
{
Cleanup();
}
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started."); if (_cancellationTokenSource != null)
{
throw new InvalidOperationException("Server is already started.");
}
_serverOptions = options; _serverOptions = options;
@@ -38,7 +45,7 @@ namespace MQTTnet.Implementations
if (options.DefaultEndpointOptions.IsEnabled) if (options.DefaultEndpointOptions.IsEnabled)
{ {
RegisterListeners(options.DefaultEndpointOptions, null, logger, _cancellationTokenSource.Token); RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token);
} }
if (options.TlsEndpointOptions?.IsEnabled == true) if (options.TlsEndpointOptions?.IsEnabled == true)
@@ -48,13 +55,7 @@ namespace MQTTnet.Implementations
throw new ArgumentException("TLS certificate is not set."); throw new ArgumentException("TLS certificate is not set.");
} }
var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate(); RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
if (!tlsCertificate.HasPrivateKey)
{
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
}
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
} }
return CompletedTask.Instance; return CompletedTask.Instance;
@@ -66,11 +67,6 @@ namespace MQTTnet.Implementations
return CompletedTask.Instance; return CompletedTask.Instance;
} }
public void Dispose()
{
Cleanup();
}
void Cleanup() void Cleanup()
{ {
try try
@@ -91,11 +87,22 @@ namespace MQTTnet.Implementations
} }
} }
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, X509Certificate2 tlsCertificate, IMqttNetLogger logger, CancellationToken cancellationToken) Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
{
var clientHandler = ClientHandler;
if (clientHandler == null)
{
return CompletedTask.Instance;
}
return clientHandler(channelAdapter);
}
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken)
{ {
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None)) if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
{ {
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, tlsCertificate, logger) var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger)
{ {
ClientHandler = OnClientAcceptedAsync ClientHandler = OnClientAcceptedAsync
}; };
@@ -108,7 +115,7 @@ namespace MQTTnet.Implementations
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None)) if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
{ {
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, tlsCertificate, logger) var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger)
{ {
ClientHandler = OnClientAcceptedAsync ClientHandler = OnClientAcceptedAsync
}; };
@@ -119,17 +126,6 @@ namespace MQTTnet.Implementations
} }
} }
} }
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
{
var clientHandler = ClientHandler;
if (clientHandler == null)
{
return CompletedTask.Instance;
}
return clientHandler(channelAdapter);
}
} }
} }
#endif #endif

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