mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 08:33:01 +08:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25b42df85c | ||
|
|
f8d614e71f | ||
|
|
2aa4219838 | ||
|
|
b98417ab39 | ||
|
|
7bb29d3c92 | ||
|
|
4d81d3f3d9 | ||
|
|
ead58dd606 | ||
|
|
a93e935585 | ||
|
|
aeb07e880e | ||
|
|
e0ce1f309e | ||
|
|
d74c81c3d1 | ||
|
|
2fa7200fb3 | ||
|
|
9f7d15e1b1 | ||
|
|
fdf00f1b18 | ||
|
|
abe7bbda15 | ||
|
|
795139d78f | ||
|
|
c7390ef0d7 | ||
|
|
8a926a3db3 | ||
|
|
7d5e49b44c | ||
|
|
555c3eac7a | ||
|
|
3937aa76e3 | ||
|
|
2090b08547 | ||
|
|
7c3fad6147 |
@@ -1,9 +1,8 @@
|
||||
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
|
||||
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
|
||||
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
|
||||
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
|
||||
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
|
||||
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
|
||||
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
|
||||
* [Server] Add support for interception of will messages (#1613).
|
||||
* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
|
||||
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
|
||||
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
|
||||
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
|
||||
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
|
||||
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
|
||||
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
|
||||
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
|
||||
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
|
||||
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "4.1.4.${{github.run_number}}"
|
||||
VERSION: "4.2.0.${{github.run_number}}"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
dotnet-version: |
|
||||
3.1.x
|
||||
6.0.x
|
||||
7.0.x
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
|
||||
+69
-1
@@ -1,4 +1,67 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=MQTTnet/@EntryIndexedValue"><?xml version="1.0" encoding="utf-16"?><Profile name="MQTTnet"><CppReformatCode>True</CppReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><HtmlReformatCode>True</HtmlReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><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" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSUpdateFileHeader>True</CSUpdateFileHeader><CppUpdateFileHeader>True</CppUpdateFileHeader><IDEA_SETTINGS>&lt;profile version="1.0"&gt;
|
||||
&lt;option name="myName" value="MQTTnet" /&gt;
|
||||
&lt;inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="JSRemoveUnnecessaryParentheses" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="TypeScriptExplicitMemberType" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="true" level="WARNING" enabled_by_default="true" /&gt;
|
||||
&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt;
|
||||
&lt;Language id="CSS"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="EditorConfig"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="HTML"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="HTTP Request"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Handlebars"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Ini"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="JSON"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Jade"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="JavaScript"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Markdown"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="Properties"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="RELAX-NG"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="SQL"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="XML"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;Rearrange&gt;true&lt;/Rearrange&gt;
|
||||
&lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;Language id="yaml"&gt;
|
||||
&lt;Reformat&gt;true&lt;/Reformat&gt;
|
||||
&lt;/Language&gt;
|
||||
&lt;/profile&gt;</RIDER_SETTINGS><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSReorderTypeMembers>True</CSReorderTypeMembers><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><CSharpFormatDocComments>True</CSharpFormatDocComments><VBFormatDocComments>True</VBFormatDocComments></Profile></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
|
||||
@@ -8,23 +71,28 @@
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INVOCATION_PARENS_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_PROPERTY_PATTERNS_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_SWITCH_EXPRESSION_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_PROPERTY_PATTERN_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_CALL_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">180</s:Int64>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PROPERTY_PATTERN/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/KeepUserLineBreaks/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||
|
||||
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client()
|
||||
{
|
||||
/*
|
||||
@@ -232,7 +254,7 @@ public static class Client_Connection_Samples
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +319,7 @@ public static class Client_Connection_Samples
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in METTv5.0.0+.
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
@@ -44,9 +44,9 @@ public sealed class Managed_Client_Simple_Samples
|
||||
Console.WriteLine("The managed MQTT client is connected.");
|
||||
|
||||
// Wait until the queue is fully processed.
|
||||
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
|
||||
SpinWait.SpinUntil(() => managedMqttClient.EnqueuedApplicationMessagesCount == 0, 10000);
|
||||
|
||||
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
|
||||
Console.WriteLine($"Pending messages = {managedMqttClient.EnqueuedApplicationMessagesCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
public static class Server_Diagnostics_Samples
|
||||
{
|
||||
public static async Task Get_Notified_When_Client_Received_Message()
|
||||
{
|
||||
/*
|
||||
* This sample starts a MQTT server and attaches an event which gets fired whenever
|
||||
* a client received and acknowledged a PUBLISH packet.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
}
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public override ValueTask DisposeAsync()
|
||||
#else
|
||||
public Task DisposeAsync()
|
||||
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
|
||||
_socket?.Dispose();
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
return base.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
@@ -51,11 +51,11 @@
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -211,8 +211,17 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
// zero copy
|
||||
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
BytesSent += buffer.Length;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
@@ -26,6 +26,10 @@
|
||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||
{
|
||||
MemoryStream stream;
|
||||
MqttPacketBuffer buffer;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
buffer = new MqttPacketBuffer(packet);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,56 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public interface IManagedMqttClient : IDisposable
|
||||
{
|
||||
event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync;
|
||||
|
||||
event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync;
|
||||
|
||||
event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync;
|
||||
|
||||
|
||||
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
||||
|
||||
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
|
||||
|
||||
|
||||
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
||||
|
||||
|
||||
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
|
||||
|
||||
event Func<EventArgs, Task> ConnectionStateChangedAsync;
|
||||
|
||||
|
||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||
|
||||
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
||||
|
||||
|
||||
event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
|
||||
|
||||
event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
|
||||
|
||||
int EnqueuedApplicationMessagesCount { get; }
|
||||
|
||||
IMqttClient InternalClient { get; }
|
||||
|
||||
|
||||
bool IsConnected { get; }
|
||||
|
||||
|
||||
bool IsStarted { get; }
|
||||
|
||||
|
||||
ManagedMqttClientOptions Options { get; }
|
||||
|
||||
int PendingApplicationMessagesCount { get; }
|
||||
|
||||
|
||||
Task EnqueueAsync(MqttApplicationMessage applicationMessage);
|
||||
|
||||
|
||||
Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage);
|
||||
|
||||
|
||||
Task PingAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task StartAsync(ManagedMqttClientOptions options);
|
||||
|
||||
Task StopAsync(bool cleanDisconnect = true);
|
||||
|
||||
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
|
||||
|
||||
Task UnsubscribeAsync(ICollection<string> topics);
|
||||
|
||||
Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
|
||||
|
||||
Task SubscribeAsync(MqttClientSubscribeOptions options);
|
||||
|
||||
Task UnsubscribeAsync(MqttClientUnsubscribeOptions options);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
||||
<RootNamespace>MQTTnet.Extensions.ManagedClient</RootNamespace>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Coptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -6,10 +6,16 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessage
|
||||
public sealed class ManagedMqttApplicationMessage
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public ManagedMqttApplicationMessage(string id, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
Id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,19 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttApplicationMessageBuilder
|
||||
public sealed class ManagedMqttApplicationMessageBuilder
|
||||
{
|
||||
private Guid _id = Guid.NewGuid();
|
||||
private MqttApplicationMessage _applicationMessage;
|
||||
MqttApplicationMessage _applicationMessage;
|
||||
string _id = Guid.NewGuid().ToString();
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
{
|
||||
_id = id;
|
||||
return this;
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
|
||||
return new ManagedMqttApplicationMessage(_id, _applicationMessage);
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
|
||||
@@ -25,7 +29,10 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(Action<MqttApplicationMessageBuilder> builder)
|
||||
{
|
||||
if (builder == null) throw new ArgumentNullException(nameof(builder));
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
var internalBuilder = new MqttApplicationMessageBuilder();
|
||||
builder(internalBuilder);
|
||||
@@ -34,18 +41,16 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
{
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
_id = id.ToString();
|
||||
return this;
|
||||
}
|
||||
|
||||
return new ManagedMqttApplicationMessage
|
||||
{
|
||||
Id = _id,
|
||||
ApplicationMessage = _applicationMessage
|
||||
};
|
||||
public ManagedMqttApplicationMessageBuilder WithId(string id)
|
||||
{
|
||||
_id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,10 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
|
||||
public sealed class ManagedMqttClient : Disposable
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
|
||||
readonly AsyncEvent<ApplicationMessageDroppedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
readonly AsyncEvent<EventArgs> _connectionStateChangedEvent = new AsyncEvent<EventArgs>();
|
||||
readonly AsyncEvent<ManagedProcessFailedEventArgs> _synchronizingSubscriptionsFailedEvent = new AsyncEvent<ManagedProcessFailedEventArgs>();
|
||||
@@ -76,7 +76,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
_logger = logger.WithSource(nameof(ManagedMqttClient));
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync
|
||||
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageSkippedAsync
|
||||
{
|
||||
add => _applicationMessageSkippedEvent.AddHandler(value);
|
||||
remove => _applicationMessageSkippedEvent.RemoveHandler(value);
|
||||
@@ -118,12 +118,18 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
remove => InternalClient.DisconnectedAsync -= value;
|
||||
}
|
||||
|
||||
public event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
|
||||
|
||||
public event Func<ManagedProcessFailedEventArgs, Task> SubscribedAsync
|
||||
{
|
||||
add => _synchronizingSubscriptionsFailedEvent.AddHandler(value);
|
||||
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueued;
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected => InternalClient.IsConnected;
|
||||
@@ -164,7 +170,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
|
||||
|
||||
ManagedMqttApplicationMessage removedMessage = null;
|
||||
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
||||
ApplicationMessageDroppedEventArgs applicationMessageDroppedEventArgs = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -175,7 +181,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
|
||||
{
|
||||
_logger.Verbose("Skipping publish of new application message because internal queue is full.");
|
||||
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
|
||||
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(applicationMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,7 +189,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
removedMessage = _messageQueue.RemoveFirst();
|
||||
_logger.Verbose("Removed oldest application message from internal queue because it is full.");
|
||||
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
|
||||
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(removedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,9 +208,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
|
||||
if (applicationMessageDroppedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
|
||||
{
|
||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
|
||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageDroppedEventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -38,6 +39,35 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return managedMqttClient.EnqueueAsync(applicationMessage);
|
||||
}
|
||||
|
||||
public static Task StopAsync(this IManagedMqttClient managedMqttClient, bool isClean = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (managedMqttClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(managedMqttClient));
|
||||
}
|
||||
|
||||
return managedMqttClient.StopAsync(new MqttClientDisconnectOptions
|
||||
{
|
||||
IsClean = isClean
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(this IManagedMqttClient managedMqttClient, MqttTopicFilter topicFilter)
|
||||
{
|
||||
if (managedMqttClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(managedMqttClient));
|
||||
}
|
||||
|
||||
if (topicFilter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilter));
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
|
||||
return managedMqttClient.SubscribeAsync(subscribeOptions);
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(
|
||||
this IManagedMqttClient managedMqttClient,
|
||||
string topic,
|
||||
@@ -53,11 +83,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
return managedMqttClient.SubscribeAsync(
|
||||
new List<MqttTopicFilter>
|
||||
{
|
||||
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
|
||||
});
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build();
|
||||
return managedMqttClient.SubscribeAsync(subscribeOptions);
|
||||
}
|
||||
|
||||
public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic)
|
||||
@@ -72,7 +99,8 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
|
||||
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||
return managedMqttClient.UnsubscribeAsync(unsubscribeOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,24 +10,54 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static class MqttFactoryExtensions
|
||||
{
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(), logger);
|
||||
}
|
||||
|
||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
|
||||
return new NewManagedMqttClient(mqttClient, logger);
|
||||
}
|
||||
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
}
|
||||
|
||||
return new NewManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
}
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
}
|
||||
|
||||
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory factory)
|
||||
{
|
||||
return new ManagedMqttClientOptionsBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessagesManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly Queue<ManagedMqttApplicationMessage> _messageQueue = new Queue<ManagedMqttApplicationMessage>();
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
public ApplicationMessagesManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ApplicationMessagesManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<ApplicationMessageDroppedEventArgs> ApplicationMessageDroppedEvent { get; } = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
|
||||
public AsyncEvent<ApplicationMessageEnqueueingEventArgs> ApplicationMessageEnqueueingEvent { get; } = new AsyncEvent<ApplicationMessageEnqueueingEventArgs>();
|
||||
|
||||
public int EnqueuedApplicationMessagesCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
return _messageQueue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dequeue()
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
_messageQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Enqueue(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
// Fire the event first so that the handler can make sure that this message is
|
||||
// properly backed up in a suitable storage.
|
||||
if (ApplicationMessageEnqueueingEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageEnqueueingEventArgs(applicationMessage, 0);
|
||||
await ApplicationMessageEnqueueingEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (_messageQueue)
|
||||
{
|
||||
|
||||
// TODO: Drop if too much!
|
||||
|
||||
_messageQueue.Enqueue(applicationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek(out ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
applicationMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
applicationMessage = _messageQueue.Peek();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,13 +6,13 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageSkippedEventArgs : EventArgs
|
||||
public sealed class ApplicationMessageDroppedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
PacketIdentifier = packetIdentifier;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
public ushort PacketIdentifier { get; }
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -15,10 +15,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Then this is _null_ the message was processed successfully without any error.
|
||||
/// Gets or sets the exception which was thrown while sending the message.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
public bool HasFailed => Exception != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
@@ -12,7 +16,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is null when the connection was failing and the server was not reachable.
|
||||
/// This is null when the server was not reachable and thus no MQTT response was transmitted.
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class SubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public SubscribeProcessedEventArgs(MqttClientSubscribeOptions options, MqttClientSubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientSubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class UnsubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public UnsubscribeProcessedEventArgs(MqttClientUnsubscribeOptions options, MqttClientUnsubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientUnsubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public enum MaintainConnectionResult
|
||||
{
|
||||
NoChange,
|
||||
|
||||
ConnectedEstablished,
|
||||
|
||||
NotConnected
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscription
|
||||
{
|
||||
public ManagedMqttSubscription(MqttClientSubscribeOptions subscribeOptions)
|
||||
{
|
||||
SubscribeOptions = subscribeOptions ?? throw new ArgumentNullException(nameof(subscribeOptions));
|
||||
|
||||
Topic = subscribeOptions.TopicFilters.First().Topic;
|
||||
}
|
||||
|
||||
public bool IsObsolete => UnsubscribeOptions != null;
|
||||
|
||||
public bool IsPending { get; set; } = true;
|
||||
|
||||
public MqttClientSubscribeOptions SubscribeOptions { get; }
|
||||
|
||||
public string Topic { get; }
|
||||
|
||||
public MqttClientUnsubscribeOptions UnsubscribeOptions { get; private set; }
|
||||
|
||||
public void MarkAsUnsubscribed(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
UnsubscribeOptions = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscriptionsManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
// Do not use a dictionary in order to preserve subscription ordering (retained messages).
|
||||
readonly List<ManagedMqttSubscription> _subscriptions = new List<ManagedMqttSubscription>();
|
||||
|
||||
public ManagedMqttSubscriptionsManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ManagedMqttSubscriptionsManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<SubscribeProcessedEventArgs> SubscribeProcessedEvent { get; } = new AsyncEvent<SubscribeProcessedEventArgs>();
|
||||
|
||||
public AsyncEvent<UnsubscribeProcessedEventArgs> UnsubscribeProcessedEvent { get; } = new AsyncEvent<UnsubscribeProcessedEventArgs>();
|
||||
|
||||
public void ResubscribeAll()
|
||||
{
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
subscription.IsPending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe(MqttClientSubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.TopicFilters.Count > 1)
|
||||
{
|
||||
throw new NotSupportedException("The managed client only supports subscribing to one topic at the same time.");
|
||||
}
|
||||
|
||||
var topic = options.TopicFilters.First().Topic;
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topic);
|
||||
|
||||
_subscriptions.RemoveAll(s => s.Topic.Equals(topic));
|
||||
_subscriptions.Add(new ManagedMqttSubscription(options));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Synchronize(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Remember real changes.
|
||||
|
||||
List<ManagedMqttSubscription> pendingSubscriptions;
|
||||
List<ManagedMqttSubscription> obsoleteSubscriptions;
|
||||
lock (_subscriptions)
|
||||
{
|
||||
pendingSubscriptions = _subscriptions.Where(s => s.IsPending).ToList();
|
||||
obsoleteSubscriptions = _subscriptions.Where(s => s.IsObsolete).ToList();
|
||||
}
|
||||
|
||||
if (pendingSubscriptions.Count == 0 && obsoleteSubscriptions.Count == 0)
|
||||
{
|
||||
_logger.Verbose("No subscriptions to synchronize");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subscription in pendingSubscriptions)
|
||||
{
|
||||
var subscribeResult = await _mqttClient.SubscribeAsync(subscription.SubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
subscription.IsPending = false;
|
||||
await FireSubscribeProcessedEvent(subscription, subscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var subscription in obsoleteSubscriptions)
|
||||
{
|
||||
var unsubscribeResult = await _mqttClient.UnsubscribeAsync(subscription.UnsubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
lock (_subscriptions)
|
||||
{
|
||||
_subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
await FireUnsubscribeProcessedEvent(subscription, unsubscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
var topic = options.TopicFilters.First();
|
||||
var existingSubscription = _subscriptions.FirstOrDefault(s => s.Topic.Equals(topic));
|
||||
|
||||
if (existingSubscription != null)
|
||||
{
|
||||
existingSubscription.MarkAsUnsubscribed(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireSubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientSubscribeResult subscribeResult)
|
||||
{
|
||||
_logger.Info("Subscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (SubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = subscribeResult?.Items.First().ResultCode ?? MqttClientSubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new SubscribeProcessedEventArgs(subscription.SubscribeOptions, resultCode, subscribeResult?.UserProperties);
|
||||
await SubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireUnsubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientUnsubscribeResult unsubscribeResult)
|
||||
{
|
||||
_logger.Info("Unsubscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (UnsubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = unsubscribeResult?.Items.First().ResultCode ?? MqttClientUnsubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new UnsubscribeProcessedEventArgs(subscription.UnsubscribeOptions, resultCode, unsubscribeResult?.UserProperties);
|
||||
await UnsubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class MqttConnectionManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
bool _wasConnected;
|
||||
|
||||
public MqttConnectionManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<MqttConnectionManager>();
|
||||
|
||||
_mqttClient.DisconnectedAsync += OnClientDisconnected;
|
||||
}
|
||||
|
||||
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
|
||||
public AsyncEvent<ConnectingFailedEventArgs> ConnectingFailedEvent { get; } = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
|
||||
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
|
||||
public async Task<MaintainConnectionResult> MaintainConnection(ManagedMqttClientOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (_wasConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The only valid way for testing the connection is to send a PING. So we start with this
|
||||
// in order to check if reconnecting etc. is require or not.
|
||||
await _mqttClient.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The client is properly connection. No further action required.
|
||||
return MaintainConnectionResult.NoChange;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while checking connection.");
|
||||
}
|
||||
}
|
||||
|
||||
MqttClientConnectResult connectResult = null;
|
||||
try
|
||||
{
|
||||
// The PING was not successful so that there is no working connection. So we connect.
|
||||
connectResult = await _mqttClient.ConnectAsync(options.ClientOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_wasConnected = true;
|
||||
_logger.Info("Connected.");
|
||||
|
||||
if (ConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
await ConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return MaintainConnectionResult.ConnectedEstablished;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (!(exception is OperationCanceledException))
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting.");
|
||||
}
|
||||
|
||||
if (ConnectingFailedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ConnectingFailedEventArgs(connectResult, exception);
|
||||
await ConnectingFailedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_wasConnected = false;
|
||||
return MaintainConnectionResult.NotConnected;
|
||||
}
|
||||
|
||||
Task OnClientDisconnected(MqttClientDisconnectedEventArgs arg)
|
||||
{
|
||||
_wasConnected = false;
|
||||
return DisconnectedEvent?.InvokeAsync(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class NewManagedMqttClient : Disposable, IManagedMqttClient
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly ApplicationMessagesManager _applicationMessagesManager;
|
||||
readonly MqttConnectionManager _connectionManager;
|
||||
readonly AsyncEvent<EventArgs> _isConnectedChangedEvent = new AsyncEvent<EventArgs>();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly ManagedMqttSubscriptionsManager _subscriptionsManager;
|
||||
|
||||
readonly ManualResetEventSlim _workSignal = new ManualResetEventSlim();
|
||||
|
||||
CancellationTokenSource _workerCancellationToken;
|
||||
Task _workerTask;
|
||||
|
||||
public NewManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
InternalClient.DisconnectedAsync += OnInternalClientDisconnected;
|
||||
|
||||
_logger = logger.WithSource(nameof(NewManagedMqttClient));
|
||||
|
||||
_connectionManager = new MqttConnectionManager(mqttClient, logger);
|
||||
_subscriptionsManager = new ManagedMqttSubscriptionsManager(mqttClient, logger);
|
||||
_applicationMessagesManager = new ApplicationMessagesManager(mqttClient, logger);
|
||||
|
||||
// TODO: Start timer for periodical connection checking.
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
|
||||
{
|
||||
add => _applicationMessageProcessedEvent.AddHandler(value);
|
||||
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||
{
|
||||
add => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
remove => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageDroppedEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageDroppedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectingFailedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectingFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||
{
|
||||
add => _connectionManager.DisconnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.DisconnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.SubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.SubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.UnsubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.UnsubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public bool IsStarted { get; private set; }
|
||||
|
||||
public ManagedMqttClientOptions Options { get; private set; }
|
||||
|
||||
public int EnqueuedApplicationMessagesCount => _applicationMessagesManager.EnqueuedApplicationMessagesCount;
|
||||
|
||||
public Task EnqueueAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
return EnqueueAsync(new ManagedMqttApplicationMessage(Guid.NewGuid().ToString(), applicationMessage));
|
||||
}
|
||||
|
||||
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
await _applicationMessagesManager.Enqueue(applicationMessage).ConfigureAwait(false);
|
||||
_workSignal.Set();
|
||||
}
|
||||
|
||||
public Task PingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return InternalClient.PingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task StartAsync(ManagedMqttClientOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
_workerCancellationToken = new CancellationTokenSource();
|
||||
|
||||
_workerTask = Task.Factory.StartNew(
|
||||
() => WorkerLoop(_workerCancellationToken.Token),
|
||||
_workerCancellationToken.Token,
|
||||
TaskCreationOptions.PreferFairness,
|
||||
TaskScheduler.Default);
|
||||
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public async Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
// TODO: Send proper Disconnect signal.
|
||||
|
||||
try
|
||||
{
|
||||
await InternalClient.DisconnectAsync(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while disconnecting.");
|
||||
}
|
||||
|
||||
_workerCancellationToken?.Cancel(false);
|
||||
_workerCancellationToken?.Dispose();
|
||||
|
||||
_workerCancellationToken = null;
|
||||
|
||||
if (_workerTask != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(MqttClientSubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Subscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Unsubscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Task OnInternalClientDisconnected(MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
_workSignal.Set();
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
async Task UpdateConnectionStatus(MaintainConnectionResult maintainConnectionResult)
|
||||
{
|
||||
var isConnected = maintainConnectionResult != MaintainConnectionResult.NotConnected;
|
||||
|
||||
if (IsConnected.Equals(isConnected))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsConnected = isConnected;
|
||||
|
||||
if (_isConnectedChangedEvent.HasHandlers)
|
||||
{
|
||||
await _isConnectedChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task WorkerLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsStarted = true;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_workSignal.Wait(cancellationToken);
|
||||
_workSignal.Reset();
|
||||
|
||||
var maintainConnectionResult = await _connectionManager.MaintainConnection(Options, cancellationToken).ConfigureAwait(false);
|
||||
await UpdateConnectionStatus(maintainConnectionResult).ConfigureAwait(false);
|
||||
|
||||
if (maintainConnectionResult == MaintainConnectionResult.ConnectedEstablished && Options.ClientOptions.CleanSession)
|
||||
{
|
||||
// Since the session is clean the server will now know any subscription so that
|
||||
// we have to subscribe to all of them again.
|
||||
_subscriptionsManager.ResubscribeAll();
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
await _subscriptionsManager.Synchronize(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
// TODO: Move into pending messages manager + locking.
|
||||
while (_applicationMessagesManager.TryPeek(out var nextPendingMessage))
|
||||
{
|
||||
Exception processingException = null;
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Publishing managed application message {0}.", nextPendingMessage.Id);
|
||||
|
||||
await InternalClient.PublishAsync(nextPendingMessage.ApplicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The message was delivered successfully. So we can get rid of it.
|
||||
_applicationMessagesManager.Dequeue();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
processingException = exception;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_applicationMessageProcessedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageProcessedEventArgs(nextPendingMessage, processingException);
|
||||
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
#if !NETSTANDARD1_3
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while maintaining connection.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsStarted = false;
|
||||
|
||||
await UpdateConnectionStatus(MaintainConnectionResult.NotConnected).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// TODO: Disconnected!
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -10,6 +10,15 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClientOptions
|
||||
{
|
||||
public ManagedMqttClientOptions()
|
||||
{
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions(MqttClientOptions clientOptions)
|
||||
{
|
||||
ClientOptions = clientOptions;
|
||||
}
|
||||
|
||||
public MqttClientOptions ClientOptions { get; set; }
|
||||
|
||||
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
|
||||
+1
-1
@@ -8,7 +8,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedMqttClientOptionsBuilder
|
||||
public sealed class ManagedMqttClientOptionsBuilder
|
||||
{
|
||||
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||
MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -17,9 +18,9 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
@@ -83,11 +84,14 @@ namespace MQTTnet.Extensions.Rpc
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithResponseTopic(responseTopic)
|
||||
.Build();
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class LowLevelMqttClient_Tests : BaseTestClass
|
||||
+124
-173
@@ -9,7 +9,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
@@ -17,82 +16,20 @@ using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||
public sealed class ManagedMqttClient_Connection_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||
|
||||
var senderClient = await testEnvironment.ConnectClient();
|
||||
|
||||
// Prepare managed client.
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||
await managedClient.SubscribeAsync("#");
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
||||
|
||||
var managedClientOptions = new ManagedMqttClientOptions
|
||||
{
|
||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||
.WithCleanSession(false)
|
||||
.Build()
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
// Send test data.
|
||||
await senderClient.PublishStringAsync("topic1");
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
|
||||
// Stop the managed client but keep session at server (not clean disconnect required).
|
||||
await managedClient.StopAsync(false);
|
||||
await LongTestDelay();
|
||||
|
||||
// Send new messages in the meantime.
|
||||
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||
await LongTestDelay();
|
||||
|
||||
// Start the managed client, it should receive the new message.
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(2);
|
||||
|
||||
// Stop and start again, no new message should be received.
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
await managedClient.StopAsync(false);
|
||||
await LongTestDelay();
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
}
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(2);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Connect_To_Invalid_Server()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
await testEnvironment.StartServer();
|
||||
await LongTestDelay();
|
||||
|
||||
var mqttClient = testEnvironment.CreateClient();
|
||||
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
|
||||
|
||||
ConnectingFailedEventArgs connectingFailedEventArgs = null;
|
||||
managedClient.ConnectingFailedAsync += args =>
|
||||
@@ -119,76 +56,63 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Drop_New_Messages_On_Full_Queue()
|
||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
var managedClient = factory.CreateManagedMqttClient();
|
||||
try
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var clientOptions = new ManagedMqttClientOptionsBuilder().WithMaxPendingMessages(5)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
|
||||
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||
|
||||
clientOptions.WithClientOptions(o => o.WithTcpServer("localhost"));
|
||||
var senderClient = await testEnvironment.ConnectClient();
|
||||
|
||||
await managedClient.StartAsync(clientOptions.Build());
|
||||
// Prepare managed client.
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||
await managedClient.SubscribeAsync("#");
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
||||
|
||||
await managedClient.EnqueueAsync("1");
|
||||
await managedClient.EnqueueAsync("2");
|
||||
await managedClient.EnqueueAsync("3");
|
||||
await managedClient.EnqueueAsync("4");
|
||||
await managedClient.EnqueueAsync("5");
|
||||
await managedClient.EnqueueAsync("6");
|
||||
await managedClient.EnqueueAsync("7");
|
||||
await managedClient.EnqueueAsync("8");
|
||||
|
||||
Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await managedClient.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ManagedClients_Will_Message_Send()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||
.WithWillTopic("My/last/will")
|
||||
.WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
|
||||
.Build();
|
||||
|
||||
var dyingClient = testEnvironment.CreateClient();
|
||||
var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
|
||||
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
// Wait until the managed client is fully set up and running.
|
||||
await Task.Delay(1000);
|
||||
|
||||
var receivingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
receivingClient.ApplicationMessageReceivedAsync += e =>
|
||||
var managedClientOptions = new ManagedMqttClientOptions
|
||||
{
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||
.WithCleanSession(false)
|
||||
.Build()
|
||||
};
|
||||
|
||||
await receivingClient.SubscribeAsync("My/last/will");
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
// Disposing the client will not sent a DISCONNECT packet so that connection is terminated
|
||||
// which will lead to the will publish.
|
||||
dyingManagedClient.Dispose();
|
||||
// Send test data.
|
||||
await senderClient.PublishStringAsync("topic1");
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
// Wait for arrival of the will message at the receiver.
|
||||
await Task.Delay(5000);
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
// Stop the managed client but keep session at server (not clean disconnect required).
|
||||
await managedClient.StopAsync(false);
|
||||
await LongTestDelay();
|
||||
|
||||
// Send new messages in the meantime.
|
||||
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||
await LongTestDelay();
|
||||
|
||||
// Start the managed client, it should receive the new message.
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(2);
|
||||
|
||||
// Stop and start again, no new message should be received.
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
await managedClient.StopAsync(false);
|
||||
await LongTestDelay();
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
}
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,18 +123,18 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await connected;
|
||||
|
||||
await managedClient.StopAsync();
|
||||
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(0, (await server.GetClientsAsync()).Count);
|
||||
}
|
||||
@@ -226,7 +150,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
var storage = new ManagedMqttClientTestStorage();
|
||||
|
||||
@@ -266,6 +190,8 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var unmanagedClient = testEnvironment.CreateClient();
|
||||
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
|
||||
|
||||
@@ -282,7 +208,7 @@ namespace MQTTnet.Tests.Client
|
||||
await managedClient.SubscribeAsync("unsubscribedThenSubscribed");
|
||||
|
||||
//wait a bit for the subscriptions to become established before the messages are published
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
@@ -312,7 +238,7 @@ namespace MQTTnet.Tests.Client
|
||||
await connected;
|
||||
|
||||
// wait a bit so that the managed client can reestablish the subscriptions
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
received = SetupReceivingOfMessages(managedClient, 2);
|
||||
|
||||
@@ -328,7 +254,7 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
var server = await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||
await sendingClient.PublishStringAsync("topic", "A", retain: true);
|
||||
@@ -338,12 +264,12 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await sendingClient.DisconnectAsync();
|
||||
|
||||
// Prepare retained messages.
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("topic").WithPayload("payload").WithRetainFlag().Build());
|
||||
|
||||
// Now use the managed client and check if subscriptions get cleared properly.
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
|
||||
|
||||
var receivedManagedMessages = new List<MqttApplicationMessage>();
|
||||
|
||||
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
|
||||
managedClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
@@ -353,7 +279,10 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||
var managedClientOptions = testEnvironment.CreateDefaultManagedMqttClientOptions();
|
||||
managedClientOptions.ClientOptions.CleanSession = false;
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, receivedManagedMessages.Count);
|
||||
@@ -361,7 +290,8 @@ namespace MQTTnet.Tests.Client
|
||||
await managedClient.StopAsync();
|
||||
await LongTestDelay();
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
// After reconnect and then some delay, the retained message must not be received,
|
||||
@@ -371,7 +301,7 @@ namespace MQTTnet.Tests.Client
|
||||
// Make sure that it gets received after subscribing again.
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
await LongTestDelay();
|
||||
|
||||
|
||||
Assert.AreEqual(2, receivedManagedMessages.Count);
|
||||
}
|
||||
}
|
||||
@@ -383,12 +313,13 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
// Use a long connection check interval to verify that the subscriptions
|
||||
// do not depend on the connection check interval anymore
|
||||
var connectionCheckInterval = TimeSpan.FromSeconds(10);
|
||||
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
|
||||
var sendingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
|
||||
|
||||
var subscribeTime = DateTime.UtcNow;
|
||||
@@ -413,7 +344,9 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var managedClient = await CreateManagedClientAsync(testEnvironment);
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
@@ -439,14 +372,52 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ManagedMqttClient> CreateManagedClientAsync(
|
||||
[TestMethod]
|
||||
public async Task Will_Message_Send()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
// Prepare the receiving client.
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
var receivingClient = await testEnvironment.ConnectClient();
|
||||
receivingClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await receivingClient.SubscribeAsync("My/last/will");
|
||||
|
||||
// Prepare the managed client.
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithWillTopic("My/last/will").Build();
|
||||
|
||||
var dyingClient = testEnvironment.CreateClient();
|
||||
var dyingManagedClient = testEnvironment.CreateManagedMqttClient(dyingClient);
|
||||
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
// Wait until the managed client is fully set up and running.
|
||||
await LongTestDelay();
|
||||
|
||||
// Disposing the client will not sent a DISCONNECT packet so that connection is terminated
|
||||
// which will lead to the will publish.
|
||||
dyingManagedClient.Dispose();
|
||||
|
||||
// Wait for arrival of the will message at the receiver.
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<IManagedMqttClient> CreateManagedClientAsync(
|
||||
TestEnvironment testEnvironment,
|
||||
IMqttClient underlyingClient = null,
|
||||
TimeSpan? connectionCheckInterval = null,
|
||||
string host = "localhost")
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer(host, testEnvironment.ServerPort);
|
||||
|
||||
var managedOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build();
|
||||
@@ -456,7 +427,7 @@ namespace MQTTnet.Tests.Client
|
||||
// at connection check intervals
|
||||
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
|
||||
|
||||
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient());
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
@@ -467,35 +438,20 @@ namespace MQTTnet.Tests.Client
|
||||
return managedClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that will finish when the
|
||||
/// <paramref
|
||||
/// name="managedClient" />
|
||||
/// has connected
|
||||
/// </summary>
|
||||
Task GetConnectedTask(ManagedMqttClient managedClient)
|
||||
static Task GetConnectedTask(IManagedMqttClient managedClient)
|
||||
{
|
||||
var connected = new TaskCompletionSource<bool>();
|
||||
var promise = new TaskCompletionSource<bool>();
|
||||
|
||||
managedClient.ConnectedAsync += e =>
|
||||
{
|
||||
connected.TrySetResult(true);
|
||||
promise.TrySetResult(true);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
return connected.Task;
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that will return the messages received on
|
||||
/// <paramref
|
||||
/// name="managedClient" />
|
||||
/// when
|
||||
/// <paramref
|
||||
/// name="expectedNumberOfMessages" />
|
||||
/// have been received
|
||||
/// </summary>
|
||||
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
static Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(IManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
{
|
||||
var receivedMessages = new List<MqttApplicationMessage>();
|
||||
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
|
||||
@@ -518,7 +474,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
|
||||
{
|
||||
IList<ManagedMqttApplicationMessage> _messages;
|
||||
IList<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>();
|
||||
|
||||
public int GetMessageCount()
|
||||
{
|
||||
@@ -527,11 +483,6 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
|
||||
{
|
||||
if (_messages == null)
|
||||
{
|
||||
_messages = new List<ManagedMqttApplicationMessage>();
|
||||
}
|
||||
|
||||
return Task.FromResult(_messages);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Tests.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Event_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Connecting_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.ConnectedAsync += e =>
|
||||
{
|
||||
eventFired = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Connecting_To_Invalid_Server_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
ConnectingFailedEventArgs connectingFailedEventArgs = null;
|
||||
managedClient.ConnectingFailedAsync += args =>
|
||||
{
|
||||
connectingFailedEventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(
|
||||
new ManagedMqttClientOptions
|
||||
{
|
||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(2)).WithTcpServer("wrong_server", 1234).Build()
|
||||
});
|
||||
|
||||
SpinWait.SpinUntil(() => connectingFailedEventArgs != null, 10000);
|
||||
|
||||
// The wrong server must be reported in general.
|
||||
Assert.IsNotNull(connectingFailedEventArgs);
|
||||
Assert.IsNotNull(connectingFailedEventArgs.Exception);
|
||||
Assert.IsNull(connectingFailedEventArgs.ConnectResult);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.DisconnectedAsync += e =>
|
||||
{
|
||||
eventFired = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
await server.StopAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Enqueueing_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.ApplicationMessageEnqueueingAsync += e =>
|
||||
{
|
||||
eventFired = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.EnqueueAsync("A");
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Subscription_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
await managedClient.SubscribeAsync("A");
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.SubscribeProcessedAsync += e =>
|
||||
{
|
||||
eventFired = e.Options.TopicFilters[0].Topic == "A";
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Unsubscription_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
await managedClient.SubscribeAsync("A");
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.UnsubscribeProcessedAsync += e =>
|
||||
{
|
||||
eventFired = e.Options.TopicFilters[0] == "A";
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
await managedClient.UnsubscribeAsync("A");
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Messages_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Drop_New_Messages_On_Full_Queue()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
var clientOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(o => o.WithTcpServer("localhost"))
|
||||
.WithMaxPendingMessages(5)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
|
||||
|
||||
await managedClient.StartAsync(clientOptions.Build());
|
||||
|
||||
await managedClient.EnqueueAsync("1");
|
||||
await managedClient.EnqueueAsync("2");
|
||||
await managedClient.EnqueueAsync("3");
|
||||
await managedClient.EnqueueAsync("4");
|
||||
await managedClient.EnqueueAsync("5");
|
||||
await managedClient.EnqueueAsync("6");
|
||||
await managedClient.EnqueueAsync("7");
|
||||
await managedClient.EnqueueAsync("8");
|
||||
|
||||
Assert.AreEqual(5, managedClient.EnqueuedApplicationMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Drop_Old_Messages_On_Full_Queue()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
var clientOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(o => o.WithTcpServer("localhost"))
|
||||
.WithMaxPendingMessages(5)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage);
|
||||
|
||||
await managedClient.StartAsync(clientOptions.Build());
|
||||
|
||||
await managedClient.EnqueueAsync("1");
|
||||
await managedClient.EnqueueAsync("2");
|
||||
await managedClient.EnqueueAsync("3");
|
||||
await managedClient.EnqueueAsync("4");
|
||||
await managedClient.EnqueueAsync("5");
|
||||
await managedClient.EnqueueAsync("6");
|
||||
await managedClient.EnqueueAsync("7");
|
||||
await managedClient.EnqueueAsync("8");
|
||||
|
||||
Assert.AreEqual(5, managedClient.EnqueuedApplicationMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Preserve_Order_Of_Subscriptions()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
// Prepare the retained messages.
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("A").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("B").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("C").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("3").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("2").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("1").WithPayload("P").WithRetainFlag().Build());
|
||||
|
||||
// Subscribe to the retained messages in a defined order.
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
var applicationMessages = new List<MqttApplicationMessage>();
|
||||
managedClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
applicationMessages.Add(e.ApplicationMessage);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.SubscribeAsync("B");
|
||||
await managedClient.SubscribeAsync("C");
|
||||
await managedClient.SubscribeAsync("A");
|
||||
await managedClient.SubscribeAsync("2");
|
||||
await managedClient.SubscribeAsync("1");
|
||||
await managedClient.SubscribeAsync("3");
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions()).ConfigureAwait(false);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
var order = string.Join(string.Empty, applicationMessages.Select(a => a.Topic));
|
||||
|
||||
Assert.AreEqual("BCA213", order);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Publish()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
testEnvironment.TrackServerReceivedApplicationMessages = true;
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = await testEnvironment.StartManagedClient();
|
||||
|
||||
await managedClient.EnqueueAsync(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("TEST").Build());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, testEnvironment.ServerReceivedApplicationMessages.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Connection_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
||||
|
||||
Assert.Fail("Must fail!");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_Custom_Reason()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_User_Properties()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.IsNotNull(eventArgs.UserProperties);
|
||||
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
||||
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
|
||||
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-46
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Tests : BaseTestClass
|
||||
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
public async Task Connect_Disconnect_Connect()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.DisconnectAsync();
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public async Task Connect_Multiple_Times_Should_Fail()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
Assert.Fail("Must fail!");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
|
||||
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -6,9 +6,8 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttClientOptionsBuilder_Tests
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -19,6 +21,8 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
public sealed class TestEnvironment : IDisposable
|
||||
@@ -28,6 +32,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
readonly List<Exception> _exceptions = new List<Exception>();
|
||||
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
||||
readonly List<IManagedMqttClient> _managedClients = new List<IManagedMqttClient>();
|
||||
readonly MqttProtocolVersion _protocolVersion;
|
||||
readonly List<string> _serverErrors = new List<string>();
|
||||
|
||||
@@ -89,8 +94,12 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public int ServerPort { get; set; }
|
||||
|
||||
public List<MqttApplicationMessage> ServerReceivedApplicationMessages { get; } = new List<MqttApplicationMessage>();
|
||||
|
||||
public TestContext TestContext { get; }
|
||||
|
||||
public bool TrackServerReceivedApplicationMessages { get; set; }
|
||||
|
||||
public Task<IMqttClient> ConnectClient()
|
||||
{
|
||||
return ConnectClient(Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion));
|
||||
@@ -238,6 +247,21 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions CreateDefaultManagedMqttClientOptions()
|
||||
{
|
||||
return Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(Factory.CreateClientOptionsBuilder().WithTcpServer("127.0.0.1", ServerPort).Build()).Build();
|
||||
}
|
||||
|
||||
public MqttClientOptions CreateDefaultClientOptions()
|
||||
{
|
||||
return CreateDefaultClientOptionsBuilder().Build();
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
||||
{
|
||||
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
|
||||
}
|
||||
|
||||
public ILowLevelMqttClient CreateLowLevelClient()
|
||||
{
|
||||
lock (_clients)
|
||||
@@ -249,6 +273,21 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public IManagedMqttClient CreateManagedClient()
|
||||
{
|
||||
return Factory.CreateManagedMqttClient(CreateClient());
|
||||
}
|
||||
|
||||
public IManagedMqttClient CreateManagedMqttClient()
|
||||
{
|
||||
return Factory.CreateManagedMqttClient(CreateClient(), ClientLogger);
|
||||
}
|
||||
|
||||
public IManagedMqttClient CreateManagedMqttClient(IMqttClient client)
|
||||
{
|
||||
return Factory.CreateManagedMqttClient(client, ClientLogger);
|
||||
}
|
||||
|
||||
public MqttServer CreateServer(MqttServerOptions options)
|
||||
{
|
||||
if (Server != null)
|
||||
@@ -260,6 +299,11 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
Server = Factory.CreateMqttServer(options, logger);
|
||||
|
||||
if (TrackServerReceivedApplicationMessages)
|
||||
{
|
||||
Server.InterceptingPublishAsync += ServerOnInterceptingPublishAsync;
|
||||
}
|
||||
|
||||
Server.ValidatingConnectionAsync += e =>
|
||||
{
|
||||
if (TestContext != null)
|
||||
@@ -296,6 +340,18 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var managedMqttClient in _managedClients)
|
||||
{
|
||||
try
|
||||
{
|
||||
managedMqttClient.StopAsync().Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
managedMqttClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var lowLevelMqttClient in _lowLevelClients)
|
||||
{
|
||||
lowLevelMqttClient.Dispose();
|
||||
@@ -322,6 +378,13 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IManagedMqttClient> StartManagedClient()
|
||||
{
|
||||
var managedClient = Factory.CreateManagedMqttClient(CreateClient());
|
||||
await managedClient.StartAsync(CreateDefaultManagedMqttClientOptions());
|
||||
return managedClient;
|
||||
}
|
||||
|
||||
public Task<MqttServer> StartServer()
|
||||
{
|
||||
return StartServer(Factory.CreateServerOptionsBuilder());
|
||||
@@ -391,5 +454,15 @@ namespace MQTTnet.Tests.Mockups
|
||||
_exceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
Task ServerOnInterceptingPublishAsync(InterceptingPublishEventArgs arg)
|
||||
{
|
||||
lock (ServerReceivedApplicationMessages)
|
||||
{
|
||||
ServerReceivedApplicationMessages.Add(arg.ApplicationMessage);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttApplicationMessageValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_TopicAlias_And_MQTT_311()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_TopicAlias_And_MQTT_500()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_UserProperties_And_MQTT_500()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(
|
||||
new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(),
|
||||
MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(
|
||||
new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(),
|
||||
MqttProtocolVersion.V311);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClientOptionsValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_UserProperties_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Throw_When_Using_UserProperties_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttTopicValidator_Tests
|
||||
public sealed class MqttTopicValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Plus()
|
||||
public void Invalid_Topic_Empty()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Empty()
|
||||
public void Invalid_Topic_Plus()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,7 +636,7 @@ namespace MQTTnet.Tests.Server
|
||||
// nothing
|
||||
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
await c1.DisconnectAsync();
|
||||
await c1.TryDisconnectAsync();
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
@@ -33,8 +33,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||
|
||||
long _bytesReceived;
|
||||
long _bytesSent;
|
||||
Statistics _statistics; // mutable struct, don't make readonly!
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
@@ -51,9 +50,9 @@ namespace MQTTnet.Adapter
|
||||
_logger = logger.WithSource(nameof(MqttChannelAdapter));
|
||||
}
|
||||
|
||||
public long BytesReceived => Interlocked.Read(ref _bytesReceived);
|
||||
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
|
||||
|
||||
public long BytesSent => Interlocked.Read(ref _bytesSent);
|
||||
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
|
||||
|
||||
public X509Certificate2 ClientCertificate => _channel.ClientCertificate;
|
||||
|
||||
@@ -147,7 +146,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
_packetInspector?.EndReceivePacket();
|
||||
|
||||
Interlocked.Add(ref _bytesSent, receivedPacket.TotalLength);
|
||||
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
||||
|
||||
if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
@@ -181,11 +180,7 @@ namespace MQTTnet.Adapter
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResetStatistics()
|
||||
{
|
||||
Interlocked.Exchange(ref _bytesReceived, 0L);
|
||||
Interlocked.Exchange(ref _bytesSent, 0L);
|
||||
}
|
||||
public void ResetStatistics() => _statistics.Reset();
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -216,7 +211,7 @@ namespace MQTTnet.Adapter
|
||||
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
|
||||
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -459,5 +454,17 @@ namespace MQTTnet.Adapter
|
||||
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
|
||||
private struct Statistics
|
||||
{
|
||||
public long _bytesReceived;
|
||||
public long _bytesSent;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Volatile.Write(ref _bytesReceived, 0);
|
||||
Volatile.Write(ref _bytesSent, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,12 +82,7 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
try
|
||||
{
|
||||
var eventArgs = new InspectMqttPacketEventArgs
|
||||
{
|
||||
Buffer = buffer,
|
||||
Direction = direction
|
||||
};
|
||||
|
||||
var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
|
||||
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -11,110 +11,112 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientConnectResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the result code.
|
||||
/// 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.
|
||||
/// Gets the client identifier which was chosen by the server.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string AssignedClientIdentifier { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication method.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the authentication data.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication method.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string AuthenticationMethod { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication data.
|
||||
/// MQTTv5 only.
|
||||
/// Gets a value indicating whether a session was already available or not.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; internal set; }
|
||||
public bool IsSessionPresent { get; internal set; }
|
||||
|
||||
public uint? MaximumPacketSize { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ReasonString { get; internal set; }
|
||||
|
||||
public ushort? ReceiveMaximum { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum QoS which is supported by the server.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the maximum QoS which is supported by the server.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response information.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the reason string.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ReasonString { get; internal set; }
|
||||
|
||||
public ushort? ReceiveMaximum { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response information.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ResponseInformation { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value for a topic alias. 0 means not supported.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the result code.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum { get; internal set; }
|
||||
public MqttClientConnectResultCode ResultCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternate server which should be used instead of the current one.
|
||||
/// MQTTv5 only.
|
||||
/// Gets whether the server supports retained messages.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ServerReference { get; internal set; }
|
||||
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.
|
||||
/// 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 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.
|
||||
/// 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.
|
||||
/// 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>
|
||||
public ushort TopicAliasMaximum { 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; }
|
||||
|
||||
/// <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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
}
|
||||
|
||||
@@ -2,20 +2,40 @@
|
||||
// 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 MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets whether this disconnect is a clean disconnect.
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
|
||||
public bool IsClean { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,62 @@
|
||||
// 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 MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptionsBuilder
|
||||
{
|
||||
MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
string _reasonString;
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
uint _sessionExpiryInterval;
|
||||
List<MqttUserProperty> _userProperties;
|
||||
|
||||
public MqttClientDisconnectOptions Build()
|
||||
{
|
||||
return new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = _reason,
|
||||
ReasonString = _reasonString
|
||||
ReasonString = _reasonString,
|
||||
UserProperties = _userProperties,
|
||||
SessionExpiryInterval = _sessionExpiryInterval
|
||||
};
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
|
||||
{
|
||||
_sessionExpiryInterval = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> userProperties)
|
||||
{
|
||||
_userProperties = userProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_userProperties == null)
|
||||
{
|
||||
_userProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_userProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum only contains values which are valid when a client sends the reason to the server.
|
||||
/// </summary>
|
||||
public enum MqttClientDisconnectOptionsReason
|
||||
{
|
||||
NormalDisconnection = 0,
|
||||
DisconnectWithWillMessage = 4,
|
||||
UnspecifiedError = 128,
|
||||
MalformedPacket = 129,
|
||||
ProtocolError = 130,
|
||||
ImplementationSpecificError = 131,
|
||||
TopicNameInvalid = 144,
|
||||
ReceiveMaximumExceeded = 147,
|
||||
TopicAliasInvalid = 148,
|
||||
PacketTooLarge = 149,
|
||||
MessageRateTooHigh = 150,
|
||||
QuotaExceeded = 151,
|
||||
AdministrativeAction = 152,
|
||||
PayloadFormatInvalid = 153
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.Formatter;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientDisconnectOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.ReasonString?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.ReasonString));
|
||||
}
|
||||
|
||||
if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection)
|
||||
{
|
||||
Throw(nameof(options.Reason));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,47 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public bool ClientWasConnected { get; internal set; }
|
||||
public MqttClientDisconnectedEventArgs(
|
||||
bool clientWasConnected,
|
||||
MqttClientConnectResult connectResult,
|
||||
MqttClientDisconnectReason reason,
|
||||
string reasonString,
|
||||
List<MqttUserProperty> userProperties,
|
||||
Exception exception)
|
||||
{
|
||||
ClientWasConnected = clientWasConnected;
|
||||
ConnectResult = connectResult;
|
||||
Exception = exception;
|
||||
Reason = reason;
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public Exception Exception { get; internal set; }
|
||||
public bool ClientWasConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets the authentication result.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; internal set; }
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; internal set; }
|
||||
|
||||
public string ReasonString { get; internal set; }
|
||||
public MqttClientDisconnectReason Reason { get; }
|
||||
|
||||
public string ReasonString { get; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
@@ -36,13 +37,20 @@ namespace MQTTnet.Client
|
||||
readonly IMqttNetLogger _rootLogger;
|
||||
|
||||
IMqttChannelAdapter _adapter;
|
||||
CancellationTokenSource _backgroundCancellationTokenSource;
|
||||
|
||||
bool _cleanDisconnectInitiated;
|
||||
volatile int _connectionStatus;
|
||||
MqttClientDisconnectReason _disconnectReason;
|
||||
|
||||
// The value for this field can be set from two different enums.
|
||||
// They contain the same values but the set is reduced in one case.
|
||||
int _disconnectReason;
|
||||
string _disconnectReasonString;
|
||||
List<MqttUserProperty> _disconnectUserProperties;
|
||||
|
||||
Task _keepAlivePacketsSenderTask;
|
||||
DateTime _lastPacketSentTimestamp;
|
||||
|
||||
CancellationTokenSource _mqttClientAlive;
|
||||
MqttPacketDispatcher _packetDispatcher;
|
||||
Task _packetReceiverTask;
|
||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||
@@ -84,25 +92,15 @@ namespace MQTTnet.Client
|
||||
add => _inspectPacketEvent.AddHandler(value);
|
||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
|
||||
public bool IsConnected => _adapter != null && (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
|
||||
public MqttClientOptions Options { get; private set; }
|
||||
|
||||
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
ThrowIfOptionsInvalid(options);
|
||||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
|
||||
@@ -118,31 +116,32 @@ namespace MQTTnet.Client
|
||||
|
||||
if (_connectingEvent.HasHandlers)
|
||||
{
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher = new MqttPacketDispatcher();
|
||||
|
||||
_backgroundCancellationTokenSource = new CancellationTokenSource();
|
||||
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
|
||||
_mqttClientAlive = new CancellationTokenSource();
|
||||
var mqttClientAliveToken = _mqttClientAlive.Token;
|
||||
|
||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
|
||||
_adapter = adapter;
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions);
|
||||
await adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
connectResult = await AuthenticateAsync(options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to the general timeout specified in the options if the user passed
|
||||
// CancellationToken.None or similar.
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
@@ -156,7 +155,7 @@ namespace MQTTnet.Client
|
||||
|
||||
if (keepAliveInterval != TimeSpan.Zero)
|
||||
{
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(mqttClientAliveToken), mqttClientAliveToken);
|
||||
}
|
||||
|
||||
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
||||
@@ -173,9 +172,12 @@ namespace MQTTnet.Client
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
|
||||
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
||||
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
if (!(exception is OperationCanceledException))
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
|
||||
|
||||
@@ -193,6 +195,7 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
|
||||
var clientWasConnected = IsConnected;
|
||||
|
||||
if (DisconnectIsPendingOrFinished())
|
||||
{
|
||||
return;
|
||||
@@ -200,14 +203,34 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
if (!clientWasConnected)
|
||||
{
|
||||
ThrowNotConnected();
|
||||
}
|
||||
|
||||
_disconnectReason = (int)options.Reason;
|
||||
_cleanDisconnectInitiated = true;
|
||||
|
||||
if (clientWasConnected)
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
||||
// must be throw to let the caller know that the disconnect is not a clean one.
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -239,6 +262,11 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||
|
||||
switch (applicationMessage.QualityOfServiceLevel)
|
||||
@@ -296,13 +324,29 @@ namespace MQTTnet.Client
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
|
||||
}
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttSubAckPacket subAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
|
||||
}
|
||||
@@ -314,13 +358,34 @@ namespace MQTTnet.Client
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
foreach (var topicFilter in options.TopicFilters)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttUnsubAckPacket unsubAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
|
||||
}
|
||||
@@ -376,7 +441,7 @@ namespace MQTTnet.Client
|
||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var clientConnectResultFactory = new MqttClientConnectResultFactory();
|
||||
result = clientConnectResultFactory.Create(connAckPacket, options.ProtocolVersion);
|
||||
result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -397,19 +462,19 @@ namespace MQTTnet.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Dispose();
|
||||
_backgroundCancellationTokenSource = null;
|
||||
_mqttClientAlive?.Dispose();
|
||||
_mqttClientAlive = null;
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
_adapter = null;
|
||||
|
||||
|
||||
_packetDispatcher?.Dispose();
|
||||
_packetDispatcher = null;
|
||||
}
|
||||
@@ -420,6 +485,26 @@ namespace MQTTnet.Client
|
||||
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
|
||||
}
|
||||
|
||||
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
|
||||
{
|
||||
var backgroundCancellationToken = _mqttClientAlive.Token;
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'.", Options.ChannelOptions);
|
||||
await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||
{
|
||||
TryInitiateDisconnect();
|
||||
@@ -446,10 +531,10 @@ namespace MQTTnet.Client
|
||||
try
|
||||
{
|
||||
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
||||
|
||||
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
|
||||
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
|
||||
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
|
||||
|
||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
|
||||
|
||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
||||
}
|
||||
@@ -465,14 +550,13 @@ namespace MQTTnet.Client
|
||||
|
||||
_logger.Info("Disconnected.");
|
||||
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs
|
||||
{
|
||||
ClientWasConnected = clientWasConnected,
|
||||
Exception = exception,
|
||||
ConnectResult = connectResult,
|
||||
Reason = _disconnectReason,
|
||||
ReasonString = _disconnectReasonString
|
||||
};
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs(
|
||||
clientWasConnected,
|
||||
connectResult,
|
||||
(MqttClientDisconnectReason)_disconnectReason,
|
||||
_disconnectReasonString,
|
||||
_disconnectUserProperties,
|
||||
exception);
|
||||
|
||||
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
||||
// when trying to reconnect in that handler etc.
|
||||
@@ -516,7 +600,8 @@ namespace MQTTnet.Client
|
||||
connectionStatus = curStatus;
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket)
|
||||
@@ -535,7 +620,6 @@ namespace MQTTnet.Client
|
||||
{
|
||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||
|
||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
|
||||
return eventArgs;
|
||||
@@ -554,8 +638,9 @@ namespace MQTTnet.Client
|
||||
|
||||
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
_disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode;
|
||||
_disconnectReason = (int)disconnectPacket.ReasonCode;
|
||||
_disconnectReasonString = disconnectPacket.ReasonString;
|
||||
_disconnectUserProperties = disconnectPacket.UserProperties;
|
||||
|
||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
||||
@@ -617,7 +702,6 @@ namespace MQTTnet.Client
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(pubAckPacket);
|
||||
}
|
||||
|
||||
@@ -645,6 +729,7 @@ namespace MQTTnet.Client
|
||||
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfNoAdapter();
|
||||
|
||||
ushort packetIdentifier = 0;
|
||||
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
|
||||
@@ -683,9 +768,9 @@ namespace MQTTnet.Client
|
||||
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfNoAdapter();
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
|
||||
return _adapter.SendPacketAsync(packet, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -693,7 +778,17 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
throw new MqttProtocolViolationException(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowIfNoAdapter()
|
||||
{
|
||||
// Having no adapter indicates that the connection is closed or still being created.
|
||||
// Thus the client is disconnected.
|
||||
if (_adapter == null)
|
||||
{
|
||||
ThrowNotConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,17 +796,40 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new MqttCommunicationException("The client is not connected.");
|
||||
ThrowNotConnected();
|
||||
}
|
||||
}
|
||||
|
||||
static void ThrowIfOptionsInvalid(MqttClientOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
if (options.ValidateFeatures)
|
||||
{
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(options);
|
||||
}
|
||||
}
|
||||
|
||||
static void ThrowNotConnected()
|
||||
{
|
||||
throw new MqttCommunicationException("The client is not connected.");
|
||||
}
|
||||
|
||||
void TryInitiateDisconnect()
|
||||
{
|
||||
lock (_disconnectLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -831,7 +949,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
exception = aggregateException.GetBaseException();
|
||||
}
|
||||
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
@@ -910,36 +1028,5 @@ namespace MQTTnet.Client
|
||||
_logger.Verbose("Stopped sending keep alive packets.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task WaitForTaskAsync(Task task, Task sender)
|
||||
{
|
||||
if (task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task == sender)
|
||||
{
|
||||
// Return here to avoid deadlocks, but first any eventual exception in the task
|
||||
// must be handled to avoid not getting an unhandled task exception
|
||||
if (!task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// By accessing the Exception property the exception is considered handled and will
|
||||
// not result in an unhandled task exception later by the finalizer
|
||||
_logger.Warning(task.Exception, "Error while waiting for background task.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientExtensions
|
||||
{
|
||||
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
||||
public static Task DisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null,
|
||||
uint sessionExpiryInterval = 0,
|
||||
List<MqttUserProperty> userProperties = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
return client.DisconnectAsync(
|
||||
new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString
|
||||
},
|
||||
CancellationToken.None);
|
||||
var disconnectOptions = new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString,
|
||||
SessionExpiryInterval = sessionExpiryInterval,
|
||||
UserProperties = userProperties
|
||||
};
|
||||
|
||||
return client.DisconnectAsync(disconnectOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishBinaryAsync(
|
||||
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
|
||||
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<bool> TryDisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore all errors.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
|
||||
@@ -14,13 +14,13 @@ namespace MQTTnet.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod { get; set; }
|
||||
|
||||
@@ -58,6 +58,10 @@ namespace MQTTnet.Client
|
||||
/// </summary>
|
||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum packet size.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint MaximumPacketSize { get; set; }
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
@@ -65,24 +69,26 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the receive messages.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request problem information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request response information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// The time after a session expires when it's not actively used.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
@@ -95,6 +101,7 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
/// This gives the maximum length of the topic alias.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum { get; set; }
|
||||
|
||||
@@ -103,8 +110,10 @@ namespace MQTTnet.Client
|
||||
/// client.
|
||||
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
|
||||
/// correctly.
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
|
||||
/// properly.
|
||||
/// <remarks>
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not
|
||||
/// connect properly.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public bool TryPrivate { get; set; } = true;
|
||||
|
||||
@@ -115,28 +124,41 @@ namespace MQTTnet.Client
|
||||
/// 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.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this feature is enabled the client will check if used properties are supported in the selected protocol
|
||||
/// version.
|
||||
/// This feature can be validated if an application message is generated one time but sent via different protocol
|
||||
/// versions.
|
||||
/// Default values are applied if the validation is off and features are not supported.
|
||||
/// </summary>
|
||||
public bool ValidateFeatures { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content type of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the correlation data of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] WillCorrelationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the will delay interval.
|
||||
/// This is the time between the client disconnect and the time the will message will be sent.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillDelayInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message expiry interval of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillMessageExpiryInterval { get; set; }
|
||||
|
||||
@@ -147,8 +169,9 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload format indicator of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; }
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the QoS level of the will message.
|
||||
@@ -157,6 +180,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response topic of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillResponseTopic { get; set; }
|
||||
|
||||
@@ -172,6 +196,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> WillUserProperties { get; set; }
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace MQTTnet.Client
|
||||
Certificates = _tlsParameters.Certificates?.ToList(),
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
||||
#endif
|
||||
};
|
||||
@@ -74,6 +74,8 @@ namespace MQTTnet.Client
|
||||
|
||||
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
||||
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
|
||||
|
||||
return _options;
|
||||
}
|
||||
|
||||
@@ -337,16 +339,6 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
@@ -13,10 +12,10 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientOptionsBuilderTlsParameters
|
||||
{
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
|
||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
#if NET48 || NETCOREAPP3_1_OR_GREATER
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
@@ -28,14 +27,14 @@ namespace MQTTnet.Client
|
||||
public IEnumerable<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get;set; }
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
#endif
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
public bool IgnoreCertificateRevocationErrors { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.WillContentType?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillContentType));
|
||||
}
|
||||
|
||||
if (options.UserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.UserProperties));
|
||||
}
|
||||
|
||||
if (options.RequestProblemInformation)
|
||||
{
|
||||
// Since this value is a boolean and true by default, validation would
|
||||
// require a nullable boolean.
|
||||
//Throw(nameof(options.RequestProblemInformation));
|
||||
}
|
||||
|
||||
if (options.RequestResponseInformation)
|
||||
{
|
||||
Throw(nameof(options.RequestResponseInformation));
|
||||
}
|
||||
|
||||
if (options.ReceiveMaximum > 0)
|
||||
{
|
||||
Throw(nameof(options.ReceiveMaximum));
|
||||
}
|
||||
|
||||
if (options.MaximumPacketSize > 0)
|
||||
{
|
||||
Throw(nameof(options.MaximumPacketSize));
|
||||
}
|
||||
|
||||
// Authentication relevant properties.
|
||||
|
||||
if (options.AuthenticationData?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.AuthenticationData));
|
||||
}
|
||||
|
||||
if (options.AuthenticationMethod?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.AuthenticationMethod));
|
||||
}
|
||||
|
||||
// Will relevant properties.
|
||||
|
||||
if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
|
||||
{
|
||||
Throw(nameof(options.WillPayloadFormatIndicator));
|
||||
}
|
||||
|
||||
if (options.WillContentType?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillContentType));
|
||||
}
|
||||
|
||||
if (options.WillCorrelationData?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillCorrelationData));
|
||||
}
|
||||
|
||||
if (options.WillResponseTopic?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillResponseTopic));
|
||||
}
|
||||
|
||||
if (options.WillDelayInterval > 0)
|
||||
{
|
||||
Throw(nameof(options.WillDelayInterval));
|
||||
}
|
||||
|
||||
if (options.WillMessageExpiryInterval > 0)
|
||||
{
|
||||
Throw(nameof(options.WillMessageExpiryInterval));
|
||||
}
|
||||
|
||||
if (options.WillUserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillUserProperties));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,13 @@ namespace MQTTnet.Client
|
||||
public List<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
|
||||
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
|
||||
#endif
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
#if NET48 || NETCOREAPP3_1_OR_GREATER
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
|
||||
@@ -9,9 +9,18 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientPublishResult
|
||||
{
|
||||
public MqttClientPublishResult(ushort? packetIdentifier, MqttClientPublishReasonCode reasonCode, string reasonString, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
PacketIdentifier = packetIdentifier;
|
||||
ReasonCode = reasonCode;
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or
|
||||
/// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the topic but overall transmit
|
||||
/// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the
|
||||
/// topic but overall transmit
|
||||
/// to the server etc. was a success.
|
||||
/// </summary>
|
||||
public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers;
|
||||
@@ -19,19 +28,19 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets the packet identifier which was used for this publish.
|
||||
/// </summary>
|
||||
public ushort? PacketIdentifier { get; set; }
|
||||
public ushort? PacketIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success;
|
||||
public MqttClientPublishReasonCode ReasonCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
public string ReasonString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
@@ -40,8 +49,8 @@ namespace MQTTnet.Client
|
||||
/// 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.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,23 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientPublishResultFactory
|
||||
{
|
||||
static readonly MqttClientPublishResult EmptySuccessResult = new MqttClientPublishResult();
|
||||
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
|
||||
static readonly MqttClientPublishResult AtMostOnceSuccessResult = new MqttClientPublishResult(null, MqttClientPublishReasonCode.Success, null, EmptyUserProperties);
|
||||
|
||||
public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket)
|
||||
{
|
||||
// QoS 0 has no response. So we treat it as a success always.
|
||||
if (pubAckPacket == null)
|
||||
{
|
||||
return EmptySuccessResult;
|
||||
return AtMostOnceSuccessResult;
|
||||
}
|
||||
|
||||
var result = new MqttClientPublishResult
|
||||
{
|
||||
var result = new MqttClientPublishResult(
|
||||
pubAckPacket.PacketIdentifier,
|
||||
// Both enums have the same values. So it can be easily converted.
|
||||
ReasonCode = (MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode,
|
||||
PacketIdentifier = pubAckPacket.PacketIdentifier,
|
||||
ReasonString = pubAckPacket.ReasonString,
|
||||
UserProperties = pubAckPacket.UserProperties ?? EmptyUserProperties
|
||||
};
|
||||
(MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode,
|
||||
pubAckPacket.ReasonString,
|
||||
pubAckPacket.UserProperties ?? EmptyUserProperties);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -37,43 +35,28 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (pubRecPacket == null || pubCompPacket == null)
|
||||
{
|
||||
return new MqttClientPublishResult
|
||||
{
|
||||
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError
|
||||
};
|
||||
var packetIdentifier = pubRecPacket?.PacketIdentifier ?? pubCompPacket?.PacketIdentifier;
|
||||
return new MqttClientPublishResult(packetIdentifier, MqttClientPublishReasonCode.ImplementationSpecificError, null, EmptyUserProperties);
|
||||
}
|
||||
|
||||
MqttClientPublishResult result;
|
||||
|
||||
// The PUBCOMP is the last packet in QoS 2. So we use the results from that instead of PUBREC.
|
||||
if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound)
|
||||
{
|
||||
result = new MqttClientPublishResult
|
||||
{
|
||||
PacketIdentifier = pubCompPacket.PacketIdentifier,
|
||||
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError,
|
||||
ReasonString = pubCompPacket.ReasonString,
|
||||
UserProperties = pubCompPacket.UserProperties ?? EmptyUserProperties
|
||||
};
|
||||
|
||||
return result;
|
||||
return new MqttClientPublishResult(
|
||||
pubCompPacket.PacketIdentifier,
|
||||
MqttClientPublishReasonCode.UnspecifiedError,
|
||||
pubCompPacket.ReasonString,
|
||||
pubCompPacket.UserProperties ?? EmptyUserProperties);
|
||||
}
|
||||
|
||||
result = new MqttClientPublishResult
|
||||
{
|
||||
PacketIdentifier = pubCompPacket.PacketIdentifier,
|
||||
ReasonCode = MqttClientPublishReasonCode.Success,
|
||||
ReasonString = pubCompPacket.ReasonString,
|
||||
UserProperties = pubCompPacket.UserProperties ?? EmptyUserProperties
|
||||
};
|
||||
|
||||
var reasonCode = MqttClientPublishReasonCode.Success;
|
||||
if (pubRecPacket.ReasonCode != MqttPubRecReasonCode.Success)
|
||||
{
|
||||
// Both enums share the same values.
|
||||
result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode;
|
||||
reasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode;
|
||||
}
|
||||
|
||||
return result;
|
||||
return new MqttClientPublishResult(pubCompPacket.PacketIdentifier, reasonCode, null, pubCompPacket.UserProperties ?? EmptyUserProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,27 +10,31 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientSubscribeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a list of topic filters the client wants to subscribe to.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// </summary>
|
||||
public List<MqttTopicFilter> TopicFilters { get; set; } = new List<MqttTopicFilter>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subscription identifier.
|
||||
/// The client can specify a subscription identifier when subscribing.
|
||||
/// The broker will establish and store the mapping relationship between this subscription and subscription identifier when successfully create or modify subscription.
|
||||
/// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to the client when need to forward PUBLISH packets matching this subscription to this client.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the subscription identifier.
|
||||
/// The client can specify a subscription identifier when subscribing.
|
||||
/// The broker will establish and store the mapping relationship between this subscription and subscription identifier
|
||||
/// when successfully create or modify subscription.
|
||||
/// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to
|
||||
/// the client when need to forward PUBLISH packets matching this subscription to this client.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SubscriptionIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets 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.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets a list of topic filters the client wants to subscribe to.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// </summary>
|
||||
public List<MqttTopicFilter> TopicFilters { get; set; } = new List<MqttTopicFilter>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets 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.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,26 +14,9 @@ namespace MQTTnet.Client
|
||||
{
|
||||
readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions();
|
||||
|
||||
/// <summary>
|
||||
/// Adds the user property to the subscribe options.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <returns>A new instance of the <see cref="MqttApplicationMessageBuilder"/> class.</returns>
|
||||
public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value)
|
||||
public MqttClientSubscribeOptions Build()
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (_subscribeOptions.UserProperties == null)
|
||||
{
|
||||
_subscribeOptions.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
|
||||
return this;
|
||||
return _subscribeOptions;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
|
||||
@@ -42,7 +25,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
throw new MqttProtocolViolationException("Subscription identifier cannot be 0.");
|
||||
}
|
||||
|
||||
|
||||
_subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier;
|
||||
return this;
|
||||
}
|
||||
@@ -54,19 +37,23 @@ namespace MQTTnet.Client
|
||||
bool retainAsPublished = false,
|
||||
MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe)
|
||||
{
|
||||
return WithTopicFilter(new MqttTopicFilter
|
||||
{
|
||||
Topic = topic,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
NoLocal = noLocal,
|
||||
RetainAsPublished = retainAsPublished,
|
||||
RetainHandling = retainHandling
|
||||
});
|
||||
return WithTopicFilter(
|
||||
new MqttTopicFilter
|
||||
{
|
||||
Topic = topic,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
NoLocal = noLocal,
|
||||
RetainAsPublished = retainAsPublished,
|
||||
RetainHandling = retainHandling
|
||||
});
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action<MqttTopicFilterBuilder> topicFilterBuilder)
|
||||
{
|
||||
if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
if (topicFilterBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
}
|
||||
|
||||
var internalTopicFilterBuilder = new MqttTopicFilterBuilder();
|
||||
topicFilterBuilder(internalTopicFilterBuilder);
|
||||
@@ -76,14 +63,20 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilterBuilder topicFilterBuilder)
|
||||
{
|
||||
if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
if (topicFilterBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
}
|
||||
|
||||
return WithTopicFilter(topicFilterBuilder.Build());
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter)
|
||||
{
|
||||
if (topicFilter == null) throw new ArgumentNullException(nameof(topicFilter));
|
||||
if (topicFilter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilter));
|
||||
}
|
||||
|
||||
if (_subscribeOptions.TopicFilters == null)
|
||||
{
|
||||
@@ -95,9 +88,20 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptions Build()
|
||||
/// <summary>
|
||||
/// Adds the user property to the subscribe options.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
return _subscribeOptions;
|
||||
if (_subscribeOptions.UserProperties == null)
|
||||
{
|
||||
_subscribeOptions.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientSubscribeOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientSubscribeOptions options, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.UserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.UserProperties));
|
||||
}
|
||||
|
||||
if (options.SubscriptionIdentifier != 0)
|
||||
{
|
||||
Throw(nameof(options.SubscriptionIdentifier));
|
||||
}
|
||||
|
||||
if (options.TopicFilters?.Any(t => t.NoLocal) == true)
|
||||
{
|
||||
Throw("NoLocal");
|
||||
}
|
||||
|
||||
if (options.TopicFilters?.Any(t => t.RetainAsPublished) == true)
|
||||
{
|
||||
Throw("RetainAsPublished");
|
||||
}
|
||||
|
||||
if (options.TopicFilters?.Any(t => t.RetainHandling != MqttRetainHandling.SendAtSubscribe) == true)
|
||||
{
|
||||
Throw("RetainHandling");
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -9,23 +10,39 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientSubscribeResult
|
||||
{
|
||||
public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties which were part of the SUBACK packet.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ReasonString { get; internal set; }
|
||||
public MqttClientSubscribeResult(
|
||||
ushort packetIdentifier,
|
||||
IReadOnlyCollection<MqttClientSubscribeResultItem> items,
|
||||
string reasonString,
|
||||
IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
PacketIdentifier = packetIdentifier;
|
||||
Items = items ?? throw new ArgumentNullException(nameof(items));
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the packet identifier which was used.
|
||||
/// Gets the result for every topic filter item.
|
||||
/// </summary>
|
||||
public ushort PacketIdentifier { get; internal set; }
|
||||
public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties which were part of the SUBACK packet.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public ushort PacketIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties which were part of the SUBACK packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -16,42 +16,35 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket)
|
||||
{
|
||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
||||
if (subAckPacket == null) throw new ArgumentNullException(nameof(subAckPacket));
|
||||
|
||||
if (subscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subscribePacket));
|
||||
}
|
||||
|
||||
if (subAckPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subAckPacket));
|
||||
}
|
||||
|
||||
// MQTTv5.0.0 handling.
|
||||
if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count)
|
||||
{
|
||||
throw new MqttProtocolViolationException(
|
||||
"The reason codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
}
|
||||
|
||||
|
||||
var items = new List<MqttClientSubscribeResultItem>();
|
||||
for (var i = 0; i < subscribePacket.TopicFilters.Count; i++)
|
||||
{
|
||||
items.Add(CreateSubscribeResultItem(i, subscribePacket, subAckPacket));
|
||||
}
|
||||
|
||||
var result = new MqttClientSubscribeResult
|
||||
{
|
||||
PacketIdentifier = subAckPacket.PacketIdentifier,
|
||||
ReasonString = subAckPacket.ReasonString,
|
||||
UserProperties = subAckPacket.UserProperties ?? EmptyUserProperties,
|
||||
Items = items
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
return new MqttClientSubscribeResult(subAckPacket.PacketIdentifier, items, subAckPacket.ReasonString, subAckPacket.UserProperties ?? EmptyUserProperties);
|
||||
}
|
||||
|
||||
static MqttClientSubscribeResultItem CreateSubscribeResultItem(int index, MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket)
|
||||
{
|
||||
var resultCode = (MqttClientSubscribeResultCode) subAckPacket.ReasonCodes[index];
|
||||
|
||||
return new MqttClientSubscribeResultItem
|
||||
{
|
||||
TopicFilter = subscribePacket.TopicFilters[index],
|
||||
ResultCode = resultCode,
|
||||
};
|
||||
var resultCode = (MqttClientSubscribeResultCode)subAckPacket.ReasonCodes[index];
|
||||
return new MqttClientSubscribeResultItem(subscribePacket.TopicFilters[index], resultCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,29 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientSubscribeResultItem
|
||||
{
|
||||
public MqttClientSubscribeResultItem(MqttTopicFilter topicFilter, MqttClientSubscribeResultCode resultCode)
|
||||
{
|
||||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||
ResultCode = resultCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the topic filter.
|
||||
/// The topic filter can contain topics and wildcards.
|
||||
/// </summary>
|
||||
public MqttTopicFilter TopicFilter { get; internal set; }
|
||||
public MqttTopicFilter TopicFilter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientSubscribeResultCode ResultCode { get; internal set; }
|
||||
public MqttClientSubscribeResultCode ResultCode { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,20 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientUnsubscribeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a list of topic filters the client wants to unsubscribe from.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// Gets or sets a list of topic filters the client wants to unsubscribe from.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// </summary>
|
||||
public List<string> TopicFilters { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets 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.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets 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.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; } = new List<MqttUserProperty>();
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,48 +8,21 @@ using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public class MqttClientUnsubscribeOptionsBuilder
|
||||
public sealed class MqttClientUnsubscribeOptionsBuilder
|
||||
{
|
||||
private readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
|
||||
readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
|
||||
|
||||
/// <summary>
|
||||
/// Adds the user property to the unsubscribe options.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
/// <param name="name">The property name.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <returns>A new instance of the <see cref="MqttClientUnsubscribeOptionsBuilder"/> class.</returns>
|
||||
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value)
|
||||
public MqttClientUnsubscribeOptions Build()
|
||||
{
|
||||
if (name is null) throw new ArgumentNullException(nameof(name));
|
||||
if (value is null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return WithUserProperty(new MqttUserProperty(name, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the user property to the unsubscribe options.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
/// <param name="userProperty">The user property.</param>
|
||||
/// <returns>A new instance of the <see cref="MqttClientUnsubscribeOptionsBuilder"/> class.</returns>
|
||||
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty)
|
||||
{
|
||||
if (userProperty is null) throw new ArgumentNullException(nameof(userProperty));
|
||||
|
||||
if (_unsubscribeOptions.UserProperties is null)
|
||||
{
|
||||
_unsubscribeOptions.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_unsubscribeOptions.UserProperties.Add(userProperty);
|
||||
|
||||
return this;
|
||||
return _unsubscribeOptions;
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic)
|
||||
{
|
||||
if (topic is null) throw new ArgumentNullException(nameof(topic));
|
||||
if (topic is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
if (_unsubscribeOptions.TopicFilters is null)
|
||||
{
|
||||
@@ -63,14 +36,42 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter)
|
||||
{
|
||||
if (topicFilter is null) throw new ArgumentNullException(nameof(topicFilter));
|
||||
if (topicFilter is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilter));
|
||||
}
|
||||
|
||||
return WithTopicFilter(topicFilter.Topic);
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptions Build()
|
||||
/// <summary>
|
||||
/// Adds the user property to the unsubscribe options.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
return _unsubscribeOptions;
|
||||
return WithUserProperty(new MqttUserProperty(name, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the user property to the unsubscribe options.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty)
|
||||
{
|
||||
if (userProperty is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userProperty));
|
||||
}
|
||||
|
||||
if (_unsubscribeOptions.UserProperties is null)
|
||||
{
|
||||
_unsubscribeOptions.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_unsubscribeOptions.UserProperties.Add(userProperty);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.Formatter;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientUnsubscribeOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientUnsubscribeOptions options, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.UserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.UserProperties));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -9,23 +10,38 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientUnsubscribeResult
|
||||
{
|
||||
public IReadOnlyCollection<MqttClientUnsubscribeResultItem> Items { get; internal set; }
|
||||
public MqttClientUnsubscribeResult(
|
||||
ushort packetIdentifier,
|
||||
IReadOnlyCollection<MqttClientUnsubscribeResultItem> items,
|
||||
string reasonString,
|
||||
IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
PacketIdentifier = packetIdentifier;
|
||||
Items = items ?? throw new ArgumentNullException(nameof(items));
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties which were part of the UNSUBACK packet.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the result for every topic filter item.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
||||
|
||||
public IReadOnlyCollection<MqttClientUnsubscribeResultItem> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the packet identifier which was used.
|
||||
/// </summary>
|
||||
public string ReasonString { get; internal set; }
|
||||
|
||||
public ushort PacketIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the packet identifier which was used.
|
||||
/// Gets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort PacketIdentifier { get; internal set; }
|
||||
public string ReasonString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties which were part of the UNSUBACK packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,17 +12,23 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientUnsubscribeResultFactory
|
||||
{
|
||||
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
|
||||
|
||||
|
||||
public MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket)
|
||||
{
|
||||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
if (unsubAckPacket == null) throw new ArgumentNullException(nameof(unsubAckPacket));
|
||||
if (unsubscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
}
|
||||
|
||||
if (unsubAckPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(unsubAckPacket));
|
||||
}
|
||||
|
||||
// MQTTv3.1.1 has no reason code at all!
|
||||
if (unsubAckPacket.ReasonCodes != null && unsubAckPacket.ReasonCodes.Count != unsubscribePacket.TopicFilters.Count)
|
||||
{
|
||||
throw new MqttProtocolViolationException(
|
||||
"The return codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
}
|
||||
|
||||
var items = new List<MqttClientUnsubscribeResultItem>();
|
||||
@@ -30,33 +36,21 @@ namespace MQTTnet.Client
|
||||
{
|
||||
items.Add(CreateUnsubscribeResultItem(i, unsubscribePacket, unsubAckPacket));
|
||||
}
|
||||
|
||||
var result = new MqttClientUnsubscribeResult
|
||||
{
|
||||
PacketIdentifier = unsubAckPacket.PacketIdentifier,
|
||||
ReasonString = unsubAckPacket.ReasonString,
|
||||
UserProperties = unsubAckPacket.UserProperties ?? EmptyUserProperties,
|
||||
Items = items
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
return new MqttClientUnsubscribeResult(unsubscribePacket.PacketIdentifier, items, unsubAckPacket.ReasonString, unsubAckPacket.UserProperties ?? EmptyUserProperties);
|
||||
}
|
||||
|
||||
|
||||
static MqttClientUnsubscribeResultItem CreateUnsubscribeResultItem(int index, MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket)
|
||||
{
|
||||
var resultCode = MqttClientUnsubscribeResultCode.Success;
|
||||
|
||||
|
||||
if (unsubAckPacket.ReasonCodes != null)
|
||||
{
|
||||
// MQTTv3.1.1 has no reason code and no return code!.
|
||||
resultCode = (MqttClientUnsubscribeResultCode) unsubAckPacket.ReasonCodes[index];
|
||||
resultCode = (MqttClientUnsubscribeResultCode)unsubAckPacket.ReasonCodes[index];
|
||||
}
|
||||
|
||||
return new MqttClientUnsubscribeResultItem
|
||||
{
|
||||
TopicFilter = unsubscribePacket.TopicFilters[index],
|
||||
ResultCode = resultCode
|
||||
};
|
||||
|
||||
return new MqttClientUnsubscribeResultItem(unsubscribePacket.TopicFilters[index], resultCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,28 @@
|
||||
// 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.Client
|
||||
{
|
||||
public sealed class MqttClientUnsubscribeResultItem
|
||||
{
|
||||
public MqttClientUnsubscribeResultItem(string topicFilter, MqttClientUnsubscribeResultCode resultCode)
|
||||
{
|
||||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||
ResultCode = resultCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the topic filter.
|
||||
/// The topic filter can contain topics and wildcards.
|
||||
/// Gets or sets the result code.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string TopicFilter { get; internal set; }
|
||||
|
||||
public MqttClientUnsubscribeResultCode ResultCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the topic filter.
|
||||
/// The topic filter can contain topics and wildcards.
|
||||
/// </summary>
|
||||
public MqttClientUnsubscribeResultCode ResultCode { get; internal set; }
|
||||
public string TopicFilter { get; }
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,13 @@ namespace MQTTnet.Diagnostics
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -8,8 +8,14 @@ namespace MQTTnet.Diagnostics
|
||||
{
|
||||
public sealed class InspectMqttPacketEventArgs : EventArgs
|
||||
{
|
||||
public MqttPacketFlowDirection Direction { get; internal set; }
|
||||
public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer)
|
||||
{
|
||||
Direction = direction;
|
||||
Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
public byte[] Buffer { get; set; }
|
||||
public byte[] Buffer { get; }
|
||||
|
||||
public MqttPacketFlowDirection Direction { get; }
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ namespace MQTTnet.Diagnostics
|
||||
return "net5.0";
|
||||
#elif NET6_0
|
||||
return "net6.0";
|
||||
#elif NET7_0
|
||||
return "net7.0";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace MQTTnet.Formatter
|
||||
else
|
||||
{
|
||||
packet.ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason;
|
||||
packet.UserProperties = clientDisconnectOptions.UserProperties;
|
||||
packet.SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval;
|
||||
}
|
||||
|
||||
return packet;
|
||||
|
||||
@@ -72,8 +72,8 @@ namespace MQTTnet.Implementations
|
||||
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
|
||||
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
|
||||
#else
|
||||
get { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
|
||||
set { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
|
||||
get { throw new NotSupportedException("TcpKeepAliveInterval requires at least netcoreapp3.0."); }
|
||||
set { throw new NotSupportedException("TcpKeepAliveInterval requires at least netcoreapp3.0."); }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ namespace MQTTnet.Implementations
|
||||
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
|
||||
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
|
||||
#else
|
||||
get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
|
||||
set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
|
||||
get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least netcoreapp3.0."); }
|
||||
set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least netcoreapp3.0."); }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ namespace MQTTnet.Implementations
|
||||
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
|
||||
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
|
||||
#else
|
||||
get { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
|
||||
set { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
|
||||
get { throw new NotSupportedException("TcpKeepAliveTime requires at least netcoreapp3.0."); }
|
||||
set { throw new NotSupportedException("TcpKeepAliveTime requires at least netcoreapp3.0."); }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// This will happen when _socket.EndAccept gets called by Task library but the socket is already disposed.
|
||||
// This will happen when _socket.EndAccept_ gets called by Task library but the socket is already disposed.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -181,17 +181,18 @@ namespace MQTTnet.Implementations
|
||||
|
||||
try
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
if (_networkStream != null)
|
||||
{
|
||||
await _networkStream.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
_networkStream?.Dispose();
|
||||
#endif
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
// Workaround for: https://github.com/dotnet/corefx/issues/24430
|
||||
using (cancellationToken.Register(_socketDisposeAction))
|
||||
{
|
||||
@@ -220,7 +221,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
|
||||
// This will happen when _socket.EndConnect_ gets called by Task library but the socket is already disposed.
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
@@ -247,38 +248,44 @@ namespace MQTTnet.Implementations
|
||||
_socket.Listen(connectionBacklog);
|
||||
}
|
||||
|
||||
#if NET452 || NET461
|
||||
public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NET452 || NET461
|
||||
return await Task.Factory.FromAsync(SocketWrapper.BeginReceive, _socket.EndReceive, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
|
||||
#else
|
||||
return await _socket.ReceiveAsync(buffer, socketFlags).ConfigureAwait(false);
|
||||
#endif
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// This will happen when _socket.EndReceive gets called by Task library but the socket is already disposed.
|
||||
// This will happen when _socket.EndReceive_ gets called by Task library but the socket is already disposed.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
public Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
|
||||
{
|
||||
return _socket.ReceiveAsync(buffer, socketFlags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET452 || NET461
|
||||
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NET452 || NET461
|
||||
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
|
||||
#else
|
||||
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
|
||||
#endif
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
|
||||
// This will happen when _socket.EndSend_ gets called by Task library but the socket is already disposed.
|
||||
}
|
||||
}
|
||||
#else
|
||||
public Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
|
||||
{
|
||||
return _socket.SendAsync(buffer, socketFlags);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET452 || NET461
|
||||
sealed class SocketWrapper
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace MQTTnet.Implementations
|
||||
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
||||
try
|
||||
{
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
var sslOptions = new SslClientAuthenticationOptions
|
||||
{
|
||||
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
|
||||
@@ -127,7 +127,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER
|
||||
await sslStream.DisposeAsync().ConfigureAwait(false);
|
||||
#else
|
||||
sslStream.Dispose();
|
||||
|
||||
@@ -86,6 +86,26 @@ namespace MQTTnet.Implementations
|
||||
_socket.LingerState = _options.LingerState;
|
||||
}
|
||||
|
||||
if (_options.KeepAlive.HasValue)
|
||||
{
|
||||
_socket.KeepAlive = _options.KeepAlive.Value;
|
||||
}
|
||||
|
||||
if (_options.TcpKeepAliveInterval.HasValue)
|
||||
{
|
||||
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value;
|
||||
}
|
||||
|
||||
if (_options.TcpKeepAliveRetryCount.HasValue)
|
||||
{
|
||||
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value;
|
||||
}
|
||||
|
||||
if (_options.TcpKeepAliveTime.HasValue)
|
||||
{
|
||||
_socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value;
|
||||
}
|
||||
|
||||
_socket.Bind(_localEndPoint);
|
||||
|
||||
// Get the local endpoint back from the socket. The port may have changed.
|
||||
@@ -178,7 +198,7 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
await sslStream.AuthenticateAsServerAsync(
|
||||
new SslServerAuthenticationOptions()
|
||||
{
|
||||
|
||||
@@ -22,7 +22,11 @@ namespace MQTTnet.Internal
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
_queue.Clear();
|
||||
#else
|
||||
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>());
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -32,6 +36,16 @@ namespace MQTTnet.Internal
|
||||
_signal.Dispose();
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
if (typeof(IDisposable).IsAssignableFrom(typeof(TItem)))
|
||||
{
|
||||
while (_queue.TryDequeue(out TItem item))
|
||||
{
|
||||
(item as IDisposable).Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// 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 System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Internal
|
||||
{
|
||||
@@ -11,7 +12,8 @@ namespace MQTTnet.Internal
|
||||
{
|
||||
public static void RunInBackground(this Task task, MqttNetSourceLogger logger = null)
|
||||
{
|
||||
task?.ContinueWith(t =>
|
||||
task?.ContinueWith(
|
||||
t =>
|
||||
{
|
||||
// Consume the exception first so that we get no exception regarding the not observed exception.
|
||||
var exception = t.Exception;
|
||||
@@ -19,5 +21,41 @@ namespace MQTTnet.Internal
|
||||
},
|
||||
TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
public static async Task WaitAsync(this Task task, Task sender, MqttNetSourceLogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task == sender)
|
||||
{
|
||||
// Return here to avoid deadlocks, but first any eventual exception in the task
|
||||
// must be handled to avoid not getting an unhandled task exception
|
||||
if (!task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// By accessing the Exception property the exception is considered handled and will
|
||||
// not result in an unhandled task exception later by the finalizer
|
||||
logger.Warning(task.Exception, "Error while waiting for background task.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
</Target>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
@@ -89,4 +89,4 @@
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -55,14 +55,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the content type to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithContentType(string contentType)
|
||||
{
|
||||
_contentType = contentType;
|
||||
@@ -71,18 +65,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the correlation data to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="correlationData">
|
||||
/// The correlation data.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithCorrelationData(byte[] correlationData)
|
||||
{
|
||||
_correlationData = correlationData;
|
||||
@@ -91,18 +75,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the message expiry interval in seconds to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="messageExpiryInterval">
|
||||
/// The message expiry interval.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpiryInterval)
|
||||
{
|
||||
_messageExpiryInterval = messageExpiryInterval;
|
||||
@@ -185,18 +159,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the payload format indicator to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="payloadFormatIndicator">
|
||||
/// The payload format indicator.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadFormatIndicator payloadFormatIndicator)
|
||||
{
|
||||
_payloadFormatIndicator = payloadFormatIndicator;
|
||||
@@ -220,18 +184,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the response topic to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="responseTopic">
|
||||
/// The response topic.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithResponseTopic(string responseTopic)
|
||||
{
|
||||
_responseTopic = responseTopic;
|
||||
@@ -251,18 +205,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the subscription identifier to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="subscriptionIdentifier">
|
||||
/// The subscription identifier.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
|
||||
{
|
||||
if (_subscriptionIdentifiers == null)
|
||||
@@ -289,18 +233,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the topic alias to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="topicAlias">
|
||||
/// The topic alias.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithTopicAlias(ushort topicAlias)
|
||||
{
|
||||
_topicAlias = topicAlias;
|
||||
@@ -309,22 +243,8 @@ namespace MQTTnet
|
||||
|
||||
/// <summary>
|
||||
/// Adds the user property to the message.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// <param
|
||||
/// name="name">
|
||||
/// The property name.
|
||||
/// </param>
|
||||
/// <param
|
||||
/// name="value">
|
||||
/// The property value.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A new instance of the
|
||||
/// <see
|
||||
/// cref="MqttApplicationMessageBuilder" />
|
||||
/// class.
|
||||
/// </returns>
|
||||
public MqttApplicationMessageBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_userProperties == null)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public static class MqttApplicationMessageValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttApplicationMessage applicationMessage, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (applicationMessage.ContentType?.Any() == true)
|
||||
{
|
||||
Throw(nameof(applicationMessage.ContentType));
|
||||
}
|
||||
|
||||
if (applicationMessage.UserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(applicationMessage.UserProperties));
|
||||
}
|
||||
|
||||
if (applicationMessage.CorrelationData?.Any() == true)
|
||||
{
|
||||
Throw(nameof(applicationMessage.CorrelationData));
|
||||
}
|
||||
|
||||
if (applicationMessage.ResponseTopic?.Any() == true)
|
||||
{
|
||||
Throw(nameof(applicationMessage.ResponseTopic));
|
||||
}
|
||||
|
||||
if (applicationMessage.SubscriptionIdentifiers?.Any() == true)
|
||||
{
|
||||
Throw(nameof(applicationMessage.SubscriptionIdentifiers));
|
||||
}
|
||||
|
||||
if (applicationMessage.TopicAlias > 0)
|
||||
{
|
||||
Throw(nameof(applicationMessage.TopicAlias));
|
||||
}
|
||||
|
||||
if (applicationMessage.PayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
|
||||
{
|
||||
Throw(nameof(applicationMessage.PayloadFormatIndicator));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ namespace MQTTnet.Packets
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the sender will not receive its own published application messages.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
/// Hint: MQTT 5 feature only.
|
||||
public bool NoLocal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -27,15 +27,15 @@ namespace MQTTnet.Packets
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether messages are retained as published or not.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RetainAsPublished { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the retain handling.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttRetainHandling RetainHandling { get; set; }
|
||||
public MqttRetainHandling RetainHandling { get; set; } = MqttRetainHandling.SendAtSubscribe;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MQTT topic.
|
||||
|
||||
@@ -11,7 +11,10 @@ namespace MQTTnet.Protocol
|
||||
{
|
||||
public static void ThrowIfInvalid(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
if (applicationMessage.TopicAlias == 0)
|
||||
{
|
||||
@@ -26,7 +29,7 @@ namespace MQTTnet.Protocol
|
||||
throw new MqttProtocolViolationException("Topic should not be empty.");
|
||||
}
|
||||
|
||||
foreach(var @char in topic)
|
||||
foreach (var @char in topic)
|
||||
{
|
||||
if (@char == '+')
|
||||
{
|
||||
@@ -54,4 +57,4 @@ namespace MQTTnet.Protocol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,30 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class ClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public ClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType, string endpoint, IDictionary sessionItems)
|
||||
readonly MqttDisconnectPacket _disconnectPacket;
|
||||
|
||||
public ClientDisconnectedEventArgs(
|
||||
string clientId,
|
||||
MqttDisconnectPacket disconnectPacket,
|
||||
MqttClientDisconnectType disconnectType,
|
||||
string endpoint,
|
||||
IDictionary sessionItems)
|
||||
{
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
DisconnectType = disconnectType;
|
||||
Endpoint = endpoint;
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
|
||||
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
|
||||
_disconnectPacket = disconnectPacket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -27,9 +40,37 @@ namespace MQTTnet.Server
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason code sent by the client.
|
||||
/// Only available for clean disconnects.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string sent by the client.
|
||||
/// Only available for clean disconnects.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString => _disconnectPacket?.ReasonString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session expiry interval sent by the client.
|
||||
/// Only available for clean disconnects.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
public IDictionary SessionItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties sent by the client.
|
||||
/// Only available for clean disconnects.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class PreparingSessionEventArgs : EventArgs
|
||||
{
|
||||
public string Id { get; internal set; }
|
||||
public string Id { get; set; }
|
||||
|
||||
// TODO: Allow adding of packets to the queue etc.
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -20,10 +19,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
readonly MqttConnectPacket _connectPacket;
|
||||
|
||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
|
||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems)
|
||||
{
|
||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,13 +34,13 @@ namespace MQTTnet.Server
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace MQTTnet.Server
|
||||
/// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is
|
||||
/// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be
|
||||
/// converted properly.
|
||||
/// MQTTv5 only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
|
||||
|
||||
@@ -114,19 +114,19 @@ namespace MQTTnet.Server
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request problem information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation => _connectPacket.RequestProblemInformation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request response information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation => _connectPacket.RequestResponseInformation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response authentication data.
|
||||
/// MQTTv5 only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] ResponseAuthenticationData { get; set; }
|
||||
|
||||
@@ -137,14 +137,14 @@ namespace MQTTnet.Server
|
||||
/// 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.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> ResponseUserProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send
|
||||
/// a different server address to the client.
|
||||
/// MQTTv5 only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ServerReference { get; set; }
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace MQTTnet.Server
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
public IDictionary SessionItems { get; internal set; }
|
||||
public IDictionary SessionItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
@@ -169,7 +169,7 @@ namespace MQTTnet.Server
|
||||
|
||||
[Obsolete("This property name has a typo. Use 'UserName' instead. This one will be removed soon.")]
|
||||
public string Username => _connectPacket.Username;
|
||||
|
||||
|
||||
public string UserName => _connectPacket.Username;
|
||||
|
||||
/// <summary>
|
||||
@@ -179,7 +179,7 @@ namespace MQTTnet.Server
|
||||
/// 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.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties => _connectPacket.UserProperties;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
@@ -13,8 +14,15 @@ namespace MQTTnet.Server
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public string SenderClientId { get; set; } = string.Empty;
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the session items which should be used for all event handlers which are involved in message
|
||||
/// processing.
|
||||
/// If _null_ is specified the singleton session items from the server are used instead.
|
||||
/// </summary>
|
||||
public IDictionary CustomSessionItems { get; set; }
|
||||
|
||||
public string SenderClientId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user