mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 01:03:01 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 |
@@ -6,3 +6,5 @@
|
||||
* [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).
|
||||
* [Server] Improved performance for retained message handling when subscribing using "DoNotSendOnSubscribe" or "SendAtSubscribeIfNewSubscriptionOnly" (#1661, thanks to @Int32Overflow).
|
||||
* [Server] Added support for changing the used TLS certificate while the server is running (#1652, thanks to @YAJeff). The certificate provider will now be invoked for every new connection!
|
||||
|
||||
+1
-69
@@ -1,67 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<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>
|
||||
@@ -71,28 +8,23 @@
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_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_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_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>
|
||||
|
||||
@@ -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.EnqueuedApplicationMessagesCount == 0, 10000);
|
||||
SpinWait.SpinUntil(() => managedMqttClient.PendingApplicationMessagesCount == 0, 10000);
|
||||
|
||||
Console.WriteLine($"Pending messages = {managedMqttClient.EnqueuedApplicationMessagesCount}");
|
||||
Console.WriteLine($"Pending messages = {managedMqttClient.PendingApplicationMessagesCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -4,7 +4,13 @@
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
Console.WriteLine("Welcome to MQTTnet samples!");
|
||||
Console.BackgroundColor = ConsoleColor.White;
|
||||
Console.BackgroundColor = ConsoleColor.Red;
|
||||
Console.Title = "Samples - MQTTnet";
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
Console.WriteLine("- Welcome to MQTTnet samples! -");
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
|
||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
|
||||
index++;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Please choose sample class (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
var input = Console.ReadLine();
|
||||
var selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
|
||||
index++;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Please choose sample (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
input = Console.ReadLine();
|
||||
selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Executing sample...");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
|
||||
try
|
||||
|
||||
@@ -17,7 +17,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_Asp_Net_Samples
|
||||
public static class Server_ASP_NET_Samples
|
||||
{
|
||||
public static Task Start_Server_With_WebSockets_Support()
|
||||
{
|
||||
@@ -0,0 +1,75 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_TLS_Samples
|
||||
{
|
||||
public static async Task Run_Server_With_Self_Signed_Certificate()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||
* It also has an encrypted connection using a self signed TLS certificate.
|
||||
*
|
||||
* See sample "Run_Minimal_Server" for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
// This certificate is self signed so that
|
||||
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||
{
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
|
||||
certRequest.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||
{
|
||||
var pfxCertificate = new X509Certificate2(
|
||||
certificate.Export(X509ContentType.Pfx),
|
||||
(string)null!,
|
||||
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||
|
||||
return pfxCertificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -15,12 +15,10 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception which was thrown while sending the message.
|
||||
/// Then this is _null_ the message was processed successfully without any error.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
public bool HasFailed => Exception != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,13 +6,13 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageDroppedEventArgs : EventArgs
|
||||
public sealed class ApplicationMessageSkippedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageDroppedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
public ApplicationMessageSkippedEventArgs(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -1,7 +1,3 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
|
||||
@@ -16,7 +12,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is null when the server was not reachable and thus no MQTT response was transmitted.
|
||||
/// This is null when the connection was failing and the server was not reachable.
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
@@ -1,56 +1,52 @@
|
||||
// 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<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
|
||||
|
||||
event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
|
||||
|
||||
int EnqueuedApplicationMessagesCount { get; }
|
||||
|
||||
|
||||
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
||||
|
||||
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(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
|
||||
|
||||
Task SubscribeAsync(MqttClientSubscribeOptions options);
|
||||
|
||||
Task UnsubscribeAsync(MqttClientUnsubscribeOptions options);
|
||||
|
||||
Task StopAsync(bool cleanDisconnect = true);
|
||||
|
||||
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
|
||||
|
||||
Task UnsubscribeAsync(ICollection<string> topics);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,7 @@
|
||||
<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
@@ -1,4 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=new_005Coptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -6,16 +6,10 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttApplicationMessage
|
||||
public class ManagedMqttApplicationMessage
|
||||
{
|
||||
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 Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public MqttApplicationMessage ApplicationMessage { get; }
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,15 @@ using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttApplicationMessageBuilder
|
||||
public class ManagedMqttApplicationMessageBuilder
|
||||
{
|
||||
MqttApplicationMessage _applicationMessage;
|
||||
string _id = Guid.NewGuid().ToString();
|
||||
private Guid _id = Guid.NewGuid();
|
||||
private MqttApplicationMessage _applicationMessage;
|
||||
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
{
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
|
||||
return new ManagedMqttApplicationMessage(_id, _applicationMessage);
|
||||
_id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithApplicationMessage(MqttApplicationMessage applicationMessage)
|
||||
@@ -29,10 +25,7 @@ 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);
|
||||
@@ -41,16 +34,18 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithId(Guid id)
|
||||
public ManagedMqttApplicationMessage Build()
|
||||
{
|
||||
_id = id.ToString();
|
||||
return this;
|
||||
}
|
||||
if (_applicationMessage == null)
|
||||
{
|
||||
throw new InvalidOperationException("The ApplicationMessage cannot be null.");
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessageBuilder WithId(string id)
|
||||
{
|
||||
_id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
return this;
|
||||
return new ManagedMqttApplicationMessage
|
||||
{
|
||||
Id = _id,
|
||||
ApplicationMessage = _applicationMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttClient : Disposable
|
||||
public sealed class ManagedMqttClient : Disposable, IManagedMqttClient
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly AsyncEvent<ApplicationMessageDroppedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
readonly AsyncEvent<ApplicationMessageSkippedEventArgs> _applicationMessageSkippedEvent = new AsyncEvent<ApplicationMessageSkippedEventArgs>();
|
||||
readonly AsyncEvent<ConnectingFailedEventArgs> _connectingFailedEvent = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
readonly AsyncEvent<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<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageSkippedAsync
|
||||
public event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync
|
||||
{
|
||||
add => _applicationMessageSkippedEvent.AddHandler(value);
|
||||
remove => _applicationMessageSkippedEvent.RemoveHandler(value);
|
||||
@@ -118,18 +118,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
remove => InternalClient.DisconnectedAsync -= value;
|
||||
}
|
||||
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync;
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync;
|
||||
|
||||
public event Func<ManagedProcessFailedEventArgs, Task> SubscribedAsync
|
||||
public event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync
|
||||
{
|
||||
add => _synchronizingSubscriptionsFailedEvent.AddHandler(value);
|
||||
remove => _synchronizingSubscriptionsFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueued;
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected => InternalClient.IsConnected;
|
||||
@@ -170,7 +164,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
|
||||
|
||||
ManagedMqttApplicationMessage removedMessage = null;
|
||||
ApplicationMessageDroppedEventArgs applicationMessageDroppedEventArgs = null;
|
||||
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -181,7 +175,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
if (Options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
|
||||
{
|
||||
_logger.Verbose("Skipping publish of new application message because internal queue is full.");
|
||||
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(applicationMessage);
|
||||
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(applicationMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,7 +183,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
removedMessage = _messageQueue.RemoveFirst();
|
||||
_logger.Verbose("Removed oldest application message from internal queue because it is full.");
|
||||
applicationMessageDroppedEventArgs = new ApplicationMessageDroppedEventArgs(removedMessage);
|
||||
applicationMessageSkippedEventArgs = new ApplicationMessageSkippedEventArgs(removedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +202,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (applicationMessageDroppedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
|
||||
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
|
||||
{
|
||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageDroppedEventArgs).ConfigureAwait(false);
|
||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -39,35 +38,6 @@ 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,
|
||||
@@ -83,8 +53,11 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic, qualityOfServiceLevel).Build();
|
||||
return managedMqttClient.SubscribeAsync(subscribeOptions);
|
||||
return managedMqttClient.SubscribeAsync(
|
||||
new List<MqttTopicFilter>
|
||||
{
|
||||
new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()
|
||||
});
|
||||
}
|
||||
|
||||
public static Task UnsubscribeAsync(this IManagedMqttClient managedMqttClient, string topic)
|
||||
@@ -99,8 +72,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
var unsubscribeOptions = new MqttClientUnsubscribeOptionsBuilder().WithTopicFilter(topic).Build();
|
||||
return managedMqttClient.UnsubscribeAsync(unsubscribeOptions);
|
||||
return managedMqttClient.UnsubscribeAsync(new List<string> { topic });
|
||||
}
|
||||
}
|
||||
}
|
||||
-9
@@ -10,15 +10,6 @@ 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 sealed class ManagedMqttClientOptionsBuilder
|
||||
public class ManagedMqttClientOptionsBuilder
|
||||
{
|
||||
readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
|
||||
MqttClientOptionsBuilder _clientOptionsBuilder;
|
||||
@@ -10,54 +10,24 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static class MqttFactoryExtensions
|
||||
{
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(), logger);
|
||||
}
|
||||
|
||||
return new NewManagedMqttClient(mqttClient, logger);
|
||||
}
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new NewManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
}
|
||||
|
||||
return new NewManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
|
||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
}
|
||||
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(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();
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessagesManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly Queue<ManagedMqttApplicationMessage> _messageQueue = new Queue<ManagedMqttApplicationMessage>();
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
public ApplicationMessagesManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ApplicationMessagesManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<ApplicationMessageDroppedEventArgs> ApplicationMessageDroppedEvent { get; } = new AsyncEvent<ApplicationMessageDroppedEventArgs>();
|
||||
|
||||
public AsyncEvent<ApplicationMessageEnqueueingEventArgs> ApplicationMessageEnqueueingEvent { get; } = new AsyncEvent<ApplicationMessageEnqueueingEventArgs>();
|
||||
|
||||
public int EnqueuedApplicationMessagesCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
return _messageQueue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dequeue()
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
_messageQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Enqueue(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
// Fire the event first so that the handler can make sure that this message is
|
||||
// properly backed up in a suitable storage.
|
||||
if (ApplicationMessageEnqueueingEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageEnqueueingEventArgs(applicationMessage, 0);
|
||||
await ApplicationMessageEnqueueingEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
lock (_messageQueue)
|
||||
{
|
||||
|
||||
// TODO: Drop if too much!
|
||||
|
||||
_messageQueue.Enqueue(applicationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek(out ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
lock (_messageQueue)
|
||||
{
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
applicationMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
applicationMessage = _messageQueue.Peek();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ApplicationMessageEnqueueingEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationMessageEnqueueingEventArgs(ManagedMqttApplicationMessage applicationMessage, ushort packetIdentifier)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
PacketIdentifier = packetIdentifier;
|
||||
}
|
||||
|
||||
public ManagedMqttApplicationMessage ApplicationMessage { get; }
|
||||
|
||||
public ushort PacketIdentifier { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class SubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public SubscribeProcessedEventArgs(MqttClientSubscribeOptions options, MqttClientSubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientSubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class UnsubscribeProcessedEventArgs : EventArgs
|
||||
{
|
||||
public UnsubscribeProcessedEventArgs(MqttClientUnsubscribeOptions options, MqttClientUnsubscribeResultCode result, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
Result = result;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptions Options { get; }
|
||||
|
||||
public MqttClientUnsubscribeResultCode Result { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public enum MaintainConnectionResult
|
||||
{
|
||||
NoChange,
|
||||
|
||||
ConnectedEstablished,
|
||||
|
||||
NotConnected
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MQTTnet.Client;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscription
|
||||
{
|
||||
public ManagedMqttSubscription(MqttClientSubscribeOptions subscribeOptions)
|
||||
{
|
||||
SubscribeOptions = subscribeOptions ?? throw new ArgumentNullException(nameof(subscribeOptions));
|
||||
|
||||
Topic = subscribeOptions.TopicFilters.First().Topic;
|
||||
}
|
||||
|
||||
public bool IsObsolete => UnsubscribeOptions != null;
|
||||
|
||||
public bool IsPending { get; set; } = true;
|
||||
|
||||
public MqttClientSubscribeOptions SubscribeOptions { get; }
|
||||
|
||||
public string Topic { get; }
|
||||
|
||||
public MqttClientUnsubscribeOptions UnsubscribeOptions { get; private set; }
|
||||
|
||||
public void MarkAsUnsubscribed(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
UnsubscribeOptions = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class ManagedMqttSubscriptionsManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
// Do not use a dictionary in order to preserve subscription ordering (retained messages).
|
||||
readonly List<ManagedMqttSubscription> _subscriptions = new List<ManagedMqttSubscription>();
|
||||
|
||||
public ManagedMqttSubscriptionsManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<ManagedMqttSubscriptionsManager>();
|
||||
}
|
||||
|
||||
public AsyncEvent<SubscribeProcessedEventArgs> SubscribeProcessedEvent { get; } = new AsyncEvent<SubscribeProcessedEventArgs>();
|
||||
|
||||
public AsyncEvent<UnsubscribeProcessedEventArgs> UnsubscribeProcessedEvent { get; } = new AsyncEvent<UnsubscribeProcessedEventArgs>();
|
||||
|
||||
public void ResubscribeAll()
|
||||
{
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
subscription.IsPending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Subscribe(MqttClientSubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.TopicFilters.Count > 1)
|
||||
{
|
||||
throw new NotSupportedException("The managed client only supports subscribing to one topic at the same time.");
|
||||
}
|
||||
|
||||
var topic = options.TopicFilters.First().Topic;
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topic);
|
||||
|
||||
_subscriptions.RemoveAll(s => s.Topic.Equals(topic));
|
||||
_subscriptions.Add(new ManagedMqttSubscription(options));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Synchronize(CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Remember real changes.
|
||||
|
||||
List<ManagedMqttSubscription> pendingSubscriptions;
|
||||
List<ManagedMqttSubscription> obsoleteSubscriptions;
|
||||
lock (_subscriptions)
|
||||
{
|
||||
pendingSubscriptions = _subscriptions.Where(s => s.IsPending).ToList();
|
||||
obsoleteSubscriptions = _subscriptions.Where(s => s.IsObsolete).ToList();
|
||||
}
|
||||
|
||||
if (pendingSubscriptions.Count == 0 && obsoleteSubscriptions.Count == 0)
|
||||
{
|
||||
_logger.Verbose("No subscriptions to synchronize");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subscription in pendingSubscriptions)
|
||||
{
|
||||
var subscribeResult = await _mqttClient.SubscribeAsync(subscription.SubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
subscription.IsPending = false;
|
||||
await FireSubscribeProcessedEvent(subscription, subscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var subscription in obsoleteSubscriptions)
|
||||
{
|
||||
var unsubscribeResult = await _mqttClient.UnsubscribeAsync(subscription.UnsubscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
lock (_subscriptions)
|
||||
{
|
||||
_subscriptions.Remove(subscription);
|
||||
}
|
||||
|
||||
await FireUnsubscribeProcessedEvent(subscription, unsubscribeResult).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
var topic = options.TopicFilters.First();
|
||||
var existingSubscription = _subscriptions.FirstOrDefault(s => s.Topic.Equals(topic));
|
||||
|
||||
if (existingSubscription != null)
|
||||
{
|
||||
existingSubscription.MarkAsUnsubscribed(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireSubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientSubscribeResult subscribeResult)
|
||||
{
|
||||
_logger.Info("Subscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (SubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = subscribeResult?.Items.First().ResultCode ?? MqttClientSubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new SubscribeProcessedEventArgs(subscription.SubscribeOptions, resultCode, subscribeResult?.UserProperties);
|
||||
await SubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task FireUnsubscribeProcessedEvent(ManagedMqttSubscription subscription, MqttClientUnsubscribeResult unsubscribeResult)
|
||||
{
|
||||
_logger.Info("Unsubscribed topic '{0}'.", subscription.Topic);
|
||||
|
||||
if (UnsubscribeProcessedEvent.HasHandlers)
|
||||
{
|
||||
var resultCode = unsubscribeResult?.Items.First().ResultCode ?? MqttClientUnsubscribeResultCode.UnspecifiedError;
|
||||
var eventArgs = new UnsubscribeProcessedEventArgs(subscription.UnsubscribeOptions, resultCode, unsubscribeResult?.UserProperties);
|
||||
await UnsubscribeProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class MqttConnectionManager
|
||||
{
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly IMqttClient _mqttClient;
|
||||
|
||||
bool _wasConnected;
|
||||
|
||||
public MqttConnectionManager(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger.WithSource<MqttConnectionManager>();
|
||||
|
||||
_mqttClient.DisconnectedAsync += OnClientDisconnected;
|
||||
}
|
||||
|
||||
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
|
||||
public AsyncEvent<ConnectingFailedEventArgs> ConnectingFailedEvent { get; } = new AsyncEvent<ConnectingFailedEventArgs>();
|
||||
|
||||
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
|
||||
public async Task<MaintainConnectionResult> MaintainConnection(ManagedMqttClientOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (_wasConnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The only valid way for testing the connection is to send a PING. So we start with this
|
||||
// in order to check if reconnecting etc. is require or not.
|
||||
await _mqttClient.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The client is properly connection. No further action required.
|
||||
return MaintainConnectionResult.NoChange;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while checking connection.");
|
||||
}
|
||||
}
|
||||
|
||||
MqttClientConnectResult connectResult = null;
|
||||
try
|
||||
{
|
||||
// The PING was not successful so that there is no working connection. So we connect.
|
||||
connectResult = await _mqttClient.ConnectAsync(options.ClientOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_wasConnected = true;
|
||||
_logger.Info("Connected.");
|
||||
|
||||
if (ConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
await ConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return MaintainConnectionResult.ConnectedEstablished;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (!(exception is OperationCanceledException))
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting.");
|
||||
}
|
||||
|
||||
if (ConnectingFailedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ConnectingFailedEventArgs(connectResult, exception);
|
||||
await ConnectingFailedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_wasConnected = false;
|
||||
return MaintainConnectionResult.NotConnected;
|
||||
}
|
||||
|
||||
Task OnClientDisconnected(MqttClientDisconnectedEventArgs arg)
|
||||
{
|
||||
_wasConnected = false;
|
||||
return DisconnectedEvent?.InvokeAsync(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public sealed class NewManagedMqttClient : Disposable, IManagedMqttClient
|
||||
{
|
||||
readonly AsyncEvent<ApplicationMessageProcessedEventArgs> _applicationMessageProcessedEvent = new AsyncEvent<ApplicationMessageProcessedEventArgs>();
|
||||
readonly ApplicationMessagesManager _applicationMessagesManager;
|
||||
readonly MqttConnectionManager _connectionManager;
|
||||
readonly AsyncEvent<EventArgs> _isConnectedChangedEvent = new AsyncEvent<EventArgs>();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly ManagedMqttSubscriptionsManager _subscriptionsManager;
|
||||
|
||||
readonly ManualResetEventSlim _workSignal = new ManualResetEventSlim();
|
||||
|
||||
CancellationTokenSource _workerCancellationToken;
|
||||
Task _workerTask;
|
||||
|
||||
public NewManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||
{
|
||||
InternalClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
InternalClient.DisconnectedAsync += OnInternalClientDisconnected;
|
||||
|
||||
_logger = logger.WithSource(nameof(NewManagedMqttClient));
|
||||
|
||||
_connectionManager = new MqttConnectionManager(mqttClient, logger);
|
||||
_subscriptionsManager = new ManagedMqttSubscriptionsManager(mqttClient, logger);
|
||||
_applicationMessagesManager = new ApplicationMessagesManager(mqttClient, logger);
|
||||
|
||||
// TODO: Start timer for periodical connection checking.
|
||||
}
|
||||
|
||||
public event Func<EventArgs, Task> ApplicationMessageEnqueueingAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageEnqueueingEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageProcessedEventArgs, Task> ApplicationMessageProcessedAsync
|
||||
{
|
||||
add => _applicationMessageProcessedEvent.AddHandler(value);
|
||||
remove => _applicationMessageProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||
{
|
||||
add => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
remove => InternalClient.ApplicationMessageReceivedAsync += value;
|
||||
}
|
||||
|
||||
public event Func<ApplicationMessageDroppedEventArgs, Task> ApplicationMessageDroppedAsync
|
||||
{
|
||||
add => _applicationMessagesManager.ApplicationMessageDroppedEvent.AddHandler(value);
|
||||
remove => _applicationMessagesManager.ApplicationMessageDroppedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync
|
||||
{
|
||||
add => _connectionManager.ConnectingFailedEvent.AddHandler(value);
|
||||
remove => _connectionManager.ConnectingFailedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||
{
|
||||
add => _connectionManager.DisconnectedEvent.AddHandler(value);
|
||||
remove => _connectionManager.DisconnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<SubscribeProcessedEventArgs, Task> SubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.SubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.SubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<UnsubscribeProcessedEventArgs, Task> UnsubscribeProcessedAsync
|
||||
{
|
||||
add => _subscriptionsManager.UnsubscribeProcessedEvent.AddHandler(value);
|
||||
remove => _subscriptionsManager.UnsubscribeProcessedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public IMqttClient InternalClient { get; }
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public bool IsStarted { get; private set; }
|
||||
|
||||
public ManagedMqttClientOptions Options { get; private set; }
|
||||
|
||||
public int EnqueuedApplicationMessagesCount => _applicationMessagesManager.EnqueuedApplicationMessagesCount;
|
||||
|
||||
public Task EnqueueAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
return EnqueueAsync(new ManagedMqttApplicationMessage(Guid.NewGuid().ToString(), applicationMessage));
|
||||
}
|
||||
|
||||
public async Task EnqueueAsync(ManagedMqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
|
||||
await _applicationMessagesManager.Enqueue(applicationMessage).ConfigureAwait(false);
|
||||
_workSignal.Set();
|
||||
}
|
||||
|
||||
public Task PingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return InternalClient.PingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task StartAsync(ManagedMqttClientOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
_workerCancellationToken = new CancellationTokenSource();
|
||||
|
||||
_workerTask = Task.Factory.StartNew(
|
||||
() => WorkerLoop(_workerCancellationToken.Token),
|
||||
_workerCancellationToken.Token,
|
||||
TaskCreationOptions.PreferFairness,
|
||||
TaskScheduler.Default);
|
||||
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public async Task StopAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
// TODO: Send proper Disconnect signal.
|
||||
|
||||
try
|
||||
{
|
||||
await InternalClient.DisconnectAsync(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while disconnecting.");
|
||||
}
|
||||
|
||||
_workerCancellationToken?.Cancel(false);
|
||||
_workerCancellationToken?.Dispose();
|
||||
|
||||
_workerCancellationToken = null;
|
||||
|
||||
if (_workerTask != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public Task SubscribeAsync(MqttClientSubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Subscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public Task UnsubscribeAsync(MqttClientUnsubscribeOptions options)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_subscriptionsManager.Unsubscribe(options);
|
||||
_workSignal.Set();
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Task OnInternalClientDisconnected(MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
_workSignal.Set();
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
async Task UpdateConnectionStatus(MaintainConnectionResult maintainConnectionResult)
|
||||
{
|
||||
var isConnected = maintainConnectionResult != MaintainConnectionResult.NotConnected;
|
||||
|
||||
if (IsConnected.Equals(isConnected))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsConnected = isConnected;
|
||||
|
||||
if (_isConnectedChangedEvent.HasHandlers)
|
||||
{
|
||||
await _isConnectedChangedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task WorkerLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsStarted = true;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_workSignal.Wait(cancellationToken);
|
||||
_workSignal.Reset();
|
||||
|
||||
var maintainConnectionResult = await _connectionManager.MaintainConnection(Options, cancellationToken).ConfigureAwait(false);
|
||||
await UpdateConnectionStatus(maintainConnectionResult).ConfigureAwait(false);
|
||||
|
||||
if (maintainConnectionResult == MaintainConnectionResult.ConnectedEstablished && Options.ClientOptions.CleanSession)
|
||||
{
|
||||
// Since the session is clean the server will now know any subscription so that
|
||||
// we have to subscribe to all of them again.
|
||||
_subscriptionsManager.ResubscribeAll();
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
await _subscriptionsManager.Synchronize(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
// TODO: Move into pending messages manager + locking.
|
||||
while (_applicationMessagesManager.TryPeek(out var nextPendingMessage))
|
||||
{
|
||||
Exception processingException = null;
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Publishing managed application message {0}.", nextPendingMessage.Id);
|
||||
|
||||
await InternalClient.PublishAsync(nextPendingMessage.ApplicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// The message was delivered successfully. So we can get rid of it.
|
||||
_applicationMessagesManager.Dequeue();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
processingException = exception;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_applicationMessageProcessedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ApplicationMessageProcessedEventArgs(nextPendingMessage, processingException);
|
||||
await _applicationMessageProcessedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
#if !NETSTANDARD1_3
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Error while maintaining connection.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsStarted = false;
|
||||
|
||||
await UpdateConnectionStatus(MaintainConnectionResult.NotConnected).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// TODO: Disconnected!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Tests.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Event_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Connecting_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.ConnectedAsync += e =>
|
||||
{
|
||||
eventFired = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Connecting_To_Invalid_Server_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
ConnectingFailedEventArgs connectingFailedEventArgs = null;
|
||||
managedClient.ConnectingFailedAsync += args =>
|
||||
{
|
||||
connectingFailedEventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(
|
||||
new ManagedMqttClientOptions
|
||||
{
|
||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(2)).WithTcpServer("wrong_server", 1234).Build()
|
||||
});
|
||||
|
||||
SpinWait.SpinUntil(() => connectingFailedEventArgs != null, 10000);
|
||||
|
||||
// The wrong server must be reported in general.
|
||||
Assert.IsNotNull(connectingFailedEventArgs);
|
||||
Assert.IsNotNull(connectingFailedEventArgs.Exception);
|
||||
Assert.IsNull(connectingFailedEventArgs.ConnectResult);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.DisconnectedAsync += e =>
|
||||
{
|
||||
eventFired = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
await server.StopAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Enqueueing_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.ApplicationMessageEnqueueingAsync += e =>
|
||||
{
|
||||
eventFired = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.EnqueueAsync("A");
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Subscription_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
await managedClient.SubscribeAsync("A");
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.SubscribeProcessedAsync += e =>
|
||||
{
|
||||
eventFired = e.Options.TopicFilters[0].Topic == "A";
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Unsubscription_Should_Fire_Event()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
|
||||
await managedClient.SubscribeAsync("A");
|
||||
|
||||
var eventFired = false;
|
||||
managedClient.UnsubscribeProcessedAsync += e =>
|
||||
{
|
||||
eventFired = e.Options.TopicFilters[0] == "A";
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
await managedClient.UnsubscribeAsync("A");
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Messages_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Drop_New_Messages_On_Full_Queue()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
var clientOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(o => o.WithTcpServer("localhost"))
|
||||
.WithMaxPendingMessages(5)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
|
||||
|
||||
await managedClient.StartAsync(clientOptions.Build());
|
||||
|
||||
await managedClient.EnqueueAsync("1");
|
||||
await managedClient.EnqueueAsync("2");
|
||||
await managedClient.EnqueueAsync("3");
|
||||
await managedClient.EnqueueAsync("4");
|
||||
await managedClient.EnqueueAsync("5");
|
||||
await managedClient.EnqueueAsync("6");
|
||||
await managedClient.EnqueueAsync("7");
|
||||
await managedClient.EnqueueAsync("8");
|
||||
|
||||
Assert.AreEqual(5, managedClient.EnqueuedApplicationMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Drop_Old_Messages_On_Full_Queue()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
var clientOptions = new ManagedMqttClientOptionsBuilder().WithClientOptions(o => o.WithTcpServer("localhost"))
|
||||
.WithMaxPendingMessages(5)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage);
|
||||
|
||||
await managedClient.StartAsync(clientOptions.Build());
|
||||
|
||||
await managedClient.EnqueueAsync("1");
|
||||
await managedClient.EnqueueAsync("2");
|
||||
await managedClient.EnqueueAsync("3");
|
||||
await managedClient.EnqueueAsync("4");
|
||||
await managedClient.EnqueueAsync("5");
|
||||
await managedClient.EnqueueAsync("6");
|
||||
await managedClient.EnqueueAsync("7");
|
||||
await managedClient.EnqueueAsync("8");
|
||||
|
||||
Assert.AreEqual(5, managedClient.EnqueuedApplicationMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Preserve_Order_Of_Subscriptions()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
// Prepare the retained messages.
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("A").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("B").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("C").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("3").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("2").WithPayload("P").WithRetainFlag().Build());
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("1").WithPayload("P").WithRetainFlag().Build());
|
||||
|
||||
// Subscribe to the retained messages in a defined order.
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient();
|
||||
|
||||
var applicationMessages = new List<MqttApplicationMessage>();
|
||||
managedClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
applicationMessages.Add(e.ApplicationMessage);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.SubscribeAsync("B");
|
||||
await managedClient.SubscribeAsync("C");
|
||||
await managedClient.SubscribeAsync("A");
|
||||
await managedClient.SubscribeAsync("2");
|
||||
await managedClient.SubscribeAsync("1");
|
||||
await managedClient.SubscribeAsync("3");
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions()).ConfigureAwait(false);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
var order = string.Join(string.Empty, applicationMessages.Select(a => a.Topic));
|
||||
|
||||
Assert.AreEqual("BCA213", order);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Publish()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
testEnvironment.TrackServerReceivedApplicationMessages = true;
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = await testEnvironment.StartManagedClient();
|
||||
|
||||
await managedClient.EnqueueAsync(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("TEST").Build());
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, testEnvironment.ServerReceivedApplicationMessages.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
-123
@@ -9,6 +9,7 @@ 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;
|
||||
@@ -19,17 +20,79 @@ using MQTTnet.Tests.Mockups;
|
||||
namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Connection_Tests : BaseTestClass
|
||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||
|
||||
var senderClient = await testEnvironment.ConnectClient();
|
||||
|
||||
// Prepare managed client.
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||
await managedClient.SubscribeAsync("#");
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
||||
|
||||
var managedClientOptions = new ManagedMqttClientOptions
|
||||
{
|
||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||
.WithCleanSession(false)
|
||||
.Build()
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
// Send test data.
|
||||
await senderClient.PublishStringAsync("topic1");
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
|
||||
// Stop the managed client but keep session at server (not clean disconnect required).
|
||||
await managedClient.StopAsync(false);
|
||||
await LongTestDelay();
|
||||
|
||||
// Send new messages in the meantime.
|
||||
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||
await LongTestDelay();
|
||||
|
||||
// Start the managed client, it should receive the new message.
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(2);
|
||||
|
||||
// Stop and start again, no new message should be received.
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
await managedClient.StopAsync(false);
|
||||
await LongTestDelay();
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
}
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(2);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Connect_To_Invalid_Server()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
await LongTestDelay();
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
|
||||
var mqttClient = testEnvironment.CreateClient();
|
||||
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
|
||||
|
||||
ConnectingFailedEventArgs connectingFailedEventArgs = null;
|
||||
managedClient.ConnectingFailedAsync += args =>
|
||||
@@ -56,63 +119,76 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||
public async Task Drop_New_Messages_On_Full_Queue()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
var factory = new MqttFactory();
|
||||
var managedClient = factory.CreateManagedMqttClient();
|
||||
try
|
||||
{
|
||||
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||
var clientOptions = new ManagedMqttClientOptionsBuilder().WithMaxPendingMessages(5)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
|
||||
|
||||
var senderClient = await testEnvironment.ConnectClient();
|
||||
clientOptions.WithClientOptions(o => o.WithTcpServer("localhost"));
|
||||
|
||||
// Prepare managed client.
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||
await managedClient.SubscribeAsync("#");
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
||||
await managedClient.StartAsync(clientOptions.Build());
|
||||
|
||||
var managedClientOptions = new ManagedMqttClientOptions
|
||||
await managedClient.EnqueueAsync("1");
|
||||
await managedClient.EnqueueAsync("2");
|
||||
await managedClient.EnqueueAsync("3");
|
||||
await managedClient.EnqueueAsync("4");
|
||||
await managedClient.EnqueueAsync("5");
|
||||
await managedClient.EnqueueAsync("6");
|
||||
await managedClient.EnqueueAsync("7");
|
||||
await managedClient.EnqueueAsync("8");
|
||||
|
||||
Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await managedClient.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ManagedClients_Will_Message_Send()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||
.WithWillTopic("My/last/will")
|
||||
.WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
|
||||
.Build();
|
||||
|
||||
var dyingClient = testEnvironment.CreateClient();
|
||||
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
|
||||
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
// Wait until the managed client is fully set up and running.
|
||||
await Task.Delay(1000);
|
||||
|
||||
var receivingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
receivingClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||
.WithCleanSession(false)
|
||||
.Build()
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
await receivingClient.SubscribeAsync("My/last/will");
|
||||
|
||||
// Send test data.
|
||||
await senderClient.PublishStringAsync("topic1");
|
||||
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();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
// Wait for arrival of the will message at the receiver.
|
||||
await Task.Delay(5000);
|
||||
|
||||
// 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);
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,18 +199,18 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
await managedClient.StartAsync(testEnvironment.CreateDefaultManagedMqttClientOptions());
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
await connected;
|
||||
|
||||
await managedClient.StopAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
await Task.Delay(500);
|
||||
|
||||
Assert.AreEqual(0, (await server.GetClientsAsync()).Count);
|
||||
}
|
||||
@@ -150,7 +226,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
var storage = new ManagedMqttClientTestStorage();
|
||||
|
||||
@@ -190,8 +266,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var unmanagedClient = testEnvironment.CreateClient();
|
||||
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
|
||||
|
||||
@@ -208,7 +282,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
await managedClient.SubscribeAsync("unsubscribedThenSubscribed");
|
||||
|
||||
//wait a bit for the subscriptions to become established before the messages are published
|
||||
await LongTestDelay();
|
||||
await Task.Delay(500);
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
@@ -238,7 +312,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
await connected;
|
||||
|
||||
// wait a bit so that the managed client can reestablish the subscriptions
|
||||
await LongTestDelay();
|
||||
await Task.Delay(500);
|
||||
|
||||
received = SetupReceivingOfMessages(managedClient, 2);
|
||||
|
||||
@@ -254,7 +328,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||
await sendingClient.PublishStringAsync("topic", "A", retain: true);
|
||||
@@ -264,12 +338,12 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
|
||||
await sendingClient.DisconnectAsync();
|
||||
|
||||
// Prepare retained messages.
|
||||
await server.InjectApplicationMessage(testEnvironment.Factory.CreateApplicationMessageBuilder().WithTopic("topic").WithPayload("payload").WithRetainFlag().Build());
|
||||
|
||||
// Now use the managed client and check if subscriptions get cleared properly.
|
||||
var receivedManagedMessages = new List<MqttApplicationMessage>();
|
||||
|
||||
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 =>
|
||||
{
|
||||
@@ -279,10 +353,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
|
||||
var managedClientOptions = testEnvironment.CreateDefaultManagedMqttClientOptions();
|
||||
managedClientOptions.ClientOptions.CleanSession = false;
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, receivedManagedMessages.Count);
|
||||
@@ -290,8 +361,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
await managedClient.StopAsync();
|
||||
await LongTestDelay();
|
||||
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||
await LongTestDelay();
|
||||
|
||||
// After reconnect and then some delay, the retained message must not be received,
|
||||
@@ -301,7 +371,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
// Make sure that it gets received after subscribing again.
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
await LongTestDelay();
|
||||
|
||||
|
||||
Assert.AreEqual(2, receivedManagedMessages.Count);
|
||||
}
|
||||
}
|
||||
@@ -313,13 +383,12 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
// Use a long connection check interval to verify that the subscriptions
|
||||
// do not depend on the connection check interval anymore
|
||||
var connectionCheckInterval = TimeSpan.FromSeconds(10);
|
||||
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
|
||||
var sendingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
|
||||
|
||||
var subscribeTime = DateTime.UtcNow;
|
||||
@@ -344,9 +413,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedClient();
|
||||
var managedClient = await CreateManagedClientAsync(testEnvironment);
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient();
|
||||
|
||||
@@ -372,52 +439,14 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
}
|
||||
}
|
||||
|
||||
[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(
|
||||
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> 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();
|
||||
@@ -427,7 +456,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
// at connection check intervals
|
||||
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
|
||||
|
||||
var managedClient = testEnvironment.CreateManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
@@ -438,20 +467,35 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
return managedClient;
|
||||
}
|
||||
|
||||
static Task GetConnectedTask(IManagedMqttClient managedClient)
|
||||
/// <summary>
|
||||
/// Returns a task that will finish when the
|
||||
/// <paramref
|
||||
/// name="managedClient" />
|
||||
/// has connected
|
||||
/// </summary>
|
||||
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
|
||||
{
|
||||
var promise = new TaskCompletionSource<bool>();
|
||||
var connected = new TaskCompletionSource<bool>();
|
||||
|
||||
managedClient.ConnectedAsync += e =>
|
||||
{
|
||||
promise.TrySetResult(true);
|
||||
connected.TrySetResult(true);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
return promise.Task;
|
||||
return connected.Task;
|
||||
}
|
||||
|
||||
static Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(IManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
/// <summary>
|
||||
/// Returns a task that will return the messages received on
|
||||
/// <paramref
|
||||
/// name="managedClient" />
|
||||
/// when
|
||||
/// <paramref
|
||||
/// name="expectedNumberOfMessages" />
|
||||
/// have been received
|
||||
/// </summary>
|
||||
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
{
|
||||
var receivedMessages = new List<MqttApplicationMessage>();
|
||||
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
|
||||
@@ -474,7 +518,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
|
||||
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
|
||||
{
|
||||
IList<ManagedMqttApplicationMessage> _messages = new List<ManagedMqttApplicationMessage>();
|
||||
IList<ManagedMqttApplicationMessage> _messages;
|
||||
|
||||
public int GetMessageCount()
|
||||
{
|
||||
@@ -483,6 +527,11 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
|
||||
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
|
||||
{
|
||||
if (_messages == null)
|
||||
{
|
||||
_messages = new List<ManagedMqttApplicationMessage>();
|
||||
}
|
||||
|
||||
return Task.FromResult(_messages);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -21,8 +19,6 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
public sealed class TestEnvironment : IDisposable
|
||||
@@ -32,7 +28,6 @@ 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>();
|
||||
|
||||
@@ -94,12 +89,8 @@ 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));
|
||||
@@ -247,11 +238,6 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public ManagedMqttClientOptions CreateDefaultManagedMqttClientOptions()
|
||||
{
|
||||
return Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(Factory.CreateClientOptionsBuilder().WithTcpServer("127.0.0.1", ServerPort).Build()).Build();
|
||||
}
|
||||
|
||||
public MqttClientOptions CreateDefaultClientOptions()
|
||||
{
|
||||
return CreateDefaultClientOptionsBuilder().Build();
|
||||
@@ -273,21 +259,6 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public IManagedMqttClient CreateManagedClient()
|
||||
{
|
||||
return Factory.CreateManagedMqttClient(CreateClient());
|
||||
}
|
||||
|
||||
public IManagedMqttClient CreateManagedMqttClient()
|
||||
{
|
||||
return Factory.CreateManagedMqttClient(CreateClient(), ClientLogger);
|
||||
}
|
||||
|
||||
public IManagedMqttClient CreateManagedMqttClient(IMqttClient client)
|
||||
{
|
||||
return Factory.CreateManagedMqttClient(client, ClientLogger);
|
||||
}
|
||||
|
||||
public MqttServer CreateServer(MqttServerOptions options)
|
||||
{
|
||||
if (Server != null)
|
||||
@@ -299,11 +270,6 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
Server = Factory.CreateMqttServer(options, logger);
|
||||
|
||||
if (TrackServerReceivedApplicationMessages)
|
||||
{
|
||||
Server.InterceptingPublishAsync += ServerOnInterceptingPublishAsync;
|
||||
}
|
||||
|
||||
Server.ValidatingConnectionAsync += e =>
|
||||
{
|
||||
if (TestContext != null)
|
||||
@@ -340,18 +306,6 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var managedMqttClient in _managedClients)
|
||||
{
|
||||
try
|
||||
{
|
||||
managedMqttClient.StopAsync().Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
managedMqttClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var lowLevelMqttClient in _lowLevelClients)
|
||||
{
|
||||
lowLevelMqttClient.Dispose();
|
||||
@@ -378,13 +332,6 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IManagedMqttClient> StartManagedClient()
|
||||
{
|
||||
var managedClient = Factory.CreateManagedMqttClient(CreateClient());
|
||||
await managedClient.StartAsync(CreateDefaultManagedMqttClientOptions());
|
||||
return managedClient;
|
||||
}
|
||||
|
||||
public Task<MqttServer> StartServer()
|
||||
{
|
||||
return StartServer(Factory.CreateServerOptionsBuilder());
|
||||
@@ -454,15 +401,5 @@ namespace MQTTnet.Tests.Mockups
|
||||
_exceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
Task ServerOnInterceptingPublishAsync(InterceptingPublishEventArgs arg)
|
||||
{
|
||||
lock (ServerReceivedApplicationMessages)
|
||||
{
|
||||
ServerReceivedApplicationMessages.Add(arg.ApplicationMessage);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Certificates;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class Tls_Tests : BaseTestClass
|
||||
{
|
||||
static X509Certificate2 CreateCertificate(string oid)
|
||||
{
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
|
||||
using (var rsa = RSA.Create())
|
||||
{
|
||||
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||
|
||||
certRequest.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
|
||||
|
||||
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||
{
|
||||
var pfxCertificate = new X509Certificate2(
|
||||
certificate.Export(X509ContentType.Pfx),
|
||||
(string)null,
|
||||
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||
|
||||
return pfxCertificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Tls_Swap_Test()
|
||||
{
|
||||
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
|
||||
|
||||
var firstOid = "1.3.6.1.5.5.7.3.1";
|
||||
var secondOid = "1.3.6.1.5.5.7.3.2";
|
||||
|
||||
var certificateProvider = new CertificateProvider
|
||||
{
|
||||
CurrentCertificate = CreateCertificate(firstOid)
|
||||
};
|
||||
|
||||
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
|
||||
|
||||
var serverOptions = serverOptionsBuilder.Build();
|
||||
|
||||
var server = testEnvironment.CreateServer(serverOptions);
|
||||
|
||||
var publishedCount = 0;
|
||||
server.InterceptingPublishAsync += args =>
|
||||
{
|
||||
Interlocked.Increment(ref publishedCount);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
var firstClient = await ConnectClientAsync(
|
||||
testEnvironment,
|
||||
args =>
|
||||
{
|
||||
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||
return true;
|
||||
});
|
||||
|
||||
var firstClientReceivedCount = 0;
|
||||
firstClient.ApplicationMessageReceivedAsync += args =>
|
||||
{
|
||||
Interlocked.Increment(ref firstClientReceivedCount);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await firstClient.SubscribeAsync("TestTopic1");
|
||||
|
||||
await firstClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
});
|
||||
|
||||
await testEnvironment.Server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
}));
|
||||
|
||||
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
||||
|
||||
// Validate that the certificate was switched
|
||||
var secondClient = await ConnectClientAsync(
|
||||
testEnvironment,
|
||||
args =>
|
||||
{
|
||||
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||
return true;
|
||||
});
|
||||
|
||||
var secondClientReceivedCount = 0;
|
||||
secondClient.ApplicationMessageReceivedAsync += args =>
|
||||
{
|
||||
Interlocked.Increment(ref secondClientReceivedCount);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await secondClient.SubscribeAsync("TestTopic2");
|
||||
|
||||
await firstClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic2",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
});
|
||||
|
||||
await testEnvironment.Server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic2",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
}));
|
||||
|
||||
// Ensure first client still works
|
||||
await firstClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
});
|
||||
|
||||
await testEnvironment.Server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
}));
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
Assert.AreEqual(6, publishedCount);
|
||||
Assert.AreEqual(4, firstClientReceivedCount);
|
||||
Assert.AreEqual(2, secondClientReceivedCount);
|
||||
|
||||
await server.StopAsync();
|
||||
}
|
||||
|
||||
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
|
||||
{
|
||||
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
||||
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
|
||||
.WithTcpServer("localhost", 8883)
|
||||
.WithTls(
|
||||
tls =>
|
||||
{
|
||||
tls.UseTls = true;
|
||||
tls.SslProtocol = SslProtocols.Tls12;
|
||||
tls.CertificateValidationHandler = certValidator;
|
||||
});
|
||||
|
||||
var clientOptions = clientOptionsBuilder.Build();
|
||||
return await testEnvironment.ConnectClient(clientOptions);
|
||||
}
|
||||
|
||||
sealed class CertificateProvider : ICertificateProvider
|
||||
{
|
||||
public X509Certificate2 CurrentCertificate { get; set; }
|
||||
|
||||
public X509Certificate2 GetCertificate()
|
||||
{
|
||||
return CurrentCertificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -11,112 +11,110 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientConnectResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the client identifier which was chosen by the server.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string AssignedClientIdentifier { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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 a value indicating whether a session was already available or not.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public bool IsSessionPresent { get; internal set; }
|
||||
|
||||
public uint? MaximumPacketSize { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum QoS which is supported by the server.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel MaximumQoS { 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 response information.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ResponseInformation { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result code.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the result code.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public MqttClientConnectResultCode ResultCode { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the server supports retained messages.
|
||||
/// MQTTv5 only.
|
||||
/// 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>
|
||||
/// 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.
|
||||
/// Gets the client identifier which was chosen by the server.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public ushort ServerKeepAlive { get; internal set; }
|
||||
public string AssignedClientIdentifier { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternate server which should be used instead of the current one.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the authentication method.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string ServerReference { get; internal set; }
|
||||
|
||||
public uint? SessionExpiryInterval { get; internal set; }
|
||||
public string AuthenticationMethod { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the shared subscriptions are available or not.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the authentication data.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public bool SharedSubscriptionAvailable { get; internal set; }
|
||||
public byte[] AuthenticationData { get; internal set; }
|
||||
|
||||
public uint? MaximumPacketSize { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the subscription identifiers are available or not.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the reason string.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public bool SubscriptionIdentifiersAvailable { get; internal set; }
|
||||
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.
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value for a topic alias. 0 means not supported.
|
||||
/// MQTTv5 only.
|
||||
/// 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.
|
||||
/// </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.
|
||||
/// Gets an alternate server which should be used instead of the current one.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; internal set; }
|
||||
public string ServerReference { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
|
||||
/// MQTTv5 only.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the keep alive interval which was chosen by the server instead of the
|
||||
/// keep alive interval from the client CONNECT packet.
|
||||
/// A value of 0 indicates that the feature is not used.
|
||||
/// </summary>
|
||||
public bool WildcardSubscriptionAvailable { get; internal set; }
|
||||
public ushort ServerKeepAlive { get; internal set; }
|
||||
|
||||
public uint? SessionExpiryInterval { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the subscription identifiers are available or not.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public bool SubscriptionIdentifiersAvailable { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the shared subscriptions are available or not.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public bool SharedSubscriptionAvailable { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; internal set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,9 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets whether this disconnect is a clean disconnect.
|
||||
/// </summary>
|
||||
public bool IsClean { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace MQTTnet.Client
|
||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public bool IsConnected => _adapter != null && (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
|
||||
public MqttClientOptions Options { get; private set; }
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace MQTTnet.Client
|
||||
|
||||
if (_connectingEvent.HasHandlers)
|
||||
{
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options)).ConfigureAwait(false);
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
@@ -174,10 +174,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
||||
|
||||
if (!(exception is OperationCanceledException))
|
||||
{
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
}
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
|
||||
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
|
||||
|
||||
@@ -600,8 +597,7 @@ namespace MQTTnet.Client
|
||||
connectionStatus = curStatus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
void EnqueueReceivedPublishPacket(MqttPublishPacket publishPacket)
|
||||
@@ -729,7 +725,6 @@ 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)
|
||||
@@ -768,9 +763,9 @@ namespace MQTTnet.Client
|
||||
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ThrowIfNoAdapter();
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
|
||||
return _adapter.SendPacketAsync(packet, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -782,16 +777,6 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowIfNoAdapter()
|
||||
{
|
||||
// Having no adapter indicates that the connection is closed or still being created.
|
||||
// Thus the client is disconnected.
|
||||
if (_adapter == null)
|
||||
{
|
||||
ThrowNotConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowIfNotConnected()
|
||||
{
|
||||
if (!IsConnected)
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace MQTTnet.Client
|
||||
/// Gets or sets the reason code.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientPublishReasonCode ReasonCode { get; }
|
||||
public MqttClientPublishReasonCode ReasonCode { get; } = MqttClientPublishReasonCode.Success;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
|
||||
@@ -10,11 +10,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientSubscribeResult
|
||||
{
|
||||
public MqttClientSubscribeResult(
|
||||
ushort packetIdentifier,
|
||||
IReadOnlyCollection<MqttClientSubscribeResultItem> items,
|
||||
string reasonString,
|
||||
IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
public MqttClientSubscribeResult(ushort packetIdentifier, IReadOnlyCollection<MqttClientSubscribeResultItem> items, string reasonString, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
PacketIdentifier = packetIdentifier;
|
||||
Items = items ?? throw new ArgumentNullException(nameof(items));
|
||||
@@ -23,26 +19,25 @@ namespace MQTTnet.Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result for every topic filter item.
|
||||
/// Gets the result for every topic filter item.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties which were part of the SUBACK packet.
|
||||
/// MQTTv5 only.
|
||||
/// Gets the user properties which were part of the SUBACK packet.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort PacketIdentifier { get; }
|
||||
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// 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>
|
||||
/// Gets the packet identifier which was used.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
public ushort PacketIdentifier { get; }
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,4 @@ namespace MQTTnet.Client
|
||||
/// </summary>
|
||||
public MqttClientSubscribeResultCode ResultCode { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,13 +101,6 @@ 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)
|
||||
{
|
||||
|
||||
@@ -1,44 +1,51 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !WINDOWS_UWP
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>();
|
||||
|
||||
CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
MqttServerOptions _serverOptions;
|
||||
CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
|
||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||
|
||||
public bool TreatSocketOpeningErrorAsWarning { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started.");
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
throw new InvalidOperationException("Server is already started.");
|
||||
}
|
||||
|
||||
_serverOptions = options;
|
||||
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
RegisterListeners(options.DefaultEndpointOptions, null, logger, _cancellationTokenSource.Token);
|
||||
RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions?.IsEnabled == true)
|
||||
@@ -48,13 +55,7 @@ namespace MQTTnet.Implementations
|
||||
throw new ArgumentException("TLS certificate is not set.");
|
||||
}
|
||||
|
||||
var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate();
|
||||
if (!tlsCertificate.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
}
|
||||
|
||||
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
|
||||
RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
@@ -66,11 +67,6 @@ namespace MQTTnet.Implementations
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
try
|
||||
@@ -91,11 +87,22 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, X509Certificate2 tlsCertificate, IMqttNetLogger logger, CancellationToken cancellationToken)
|
||||
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
|
||||
{
|
||||
var clientHandler = ClientHandler;
|
||||
if (clientHandler == null)
|
||||
{
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
return clientHandler(channelAdapter);
|
||||
}
|
||||
|
||||
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
|
||||
{
|
||||
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
||||
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger)
|
||||
{
|
||||
ClientHandler = OnClientAcceptedAsync
|
||||
};
|
||||
@@ -108,7 +115,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
|
||||
{
|
||||
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
||||
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger)
|
||||
{
|
||||
ClientHandler = OnClientAcceptedAsync
|
||||
};
|
||||
@@ -119,17 +126,6 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
|
||||
{
|
||||
var clientHandler = ClientHandler;
|
||||
if (clientHandler == null)
|
||||
{
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
return clientHandler(channelAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -27,8 +27,7 @@ namespace MQTTnet.Implementations
|
||||
readonly MqttServerOptions _serverOptions;
|
||||
readonly MqttServerTcpEndpointBaseOptions _options;
|
||||
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
||||
readonly X509Certificate2 _tlsCertificate;
|
||||
|
||||
|
||||
CrossPlatformSocket _socket;
|
||||
IPEndPoint _localEndPoint;
|
||||
|
||||
@@ -36,13 +35,11 @@ namespace MQTTnet.Implementations
|
||||
AddressFamily addressFamily,
|
||||
MqttServerOptions serverOptions,
|
||||
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
|
||||
X509Certificate2 tlsCertificate,
|
||||
IMqttNetLogger logger)
|
||||
{
|
||||
_addressFamily = addressFamily;
|
||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
|
||||
_tlsCertificate = tlsCertificate;
|
||||
_rootLogger = logger;
|
||||
_logger = logger.WithSource(nameof(MqttTcpServerListener));
|
||||
|
||||
@@ -66,7 +63,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
_localEndPoint = new IPEndPoint(boundIp, _options.Port);
|
||||
|
||||
_logger.Info("Starting TCP listener (Endpoint='{0}', TLS={1}).", _localEndPoint, _tlsCertificate != null);
|
||||
_logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null);
|
||||
|
||||
_socket = new CrossPlatformSocket(_addressFamily);
|
||||
|
||||
@@ -115,7 +112,7 @@ namespace MQTTnet.Implementations
|
||||
|
||||
_socket.Listen(_options.ConnectionBacklog);
|
||||
|
||||
_logger.Verbose("TCP listener started (Endpoint='{0}'.", _localEndPoint);
|
||||
_logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint);
|
||||
|
||||
Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
||||
|
||||
@@ -128,7 +125,7 @@ namespace MQTTnet.Implementations
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.Warning(exception, "Error while creating listener socket for local end point '{0}'.", _localEndPoint);
|
||||
_logger.Warning(exception, "Error while starting TCP listener (Endpoint={0})", _localEndPoint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -136,10 +133,6 @@ namespace MQTTnet.Implementations
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Dispose();
|
||||
|
||||
#if !NET452
|
||||
_tlsCertificate?.Dispose();
|
||||
#endif
|
||||
}
|
||||
|
||||
async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken)
|
||||
@@ -170,7 +163,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Error(exception, "Error while accepting connection at TCP listener {0} TLS={1}.", _localEndPoint, _tlsCertificate != null);
|
||||
_logger.Error(exception, "Error while accepting TCP connection (Endpoint={0})", _localEndPoint);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -185,24 +178,26 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
remoteEndPoint = clientSocket.RemoteEndPoint.ToString();
|
||||
|
||||
_logger.Verbose("Client '{0}' accepted by TCP listener '{1}, {2}'.",
|
||||
remoteEndPoint,
|
||||
_localEndPoint,
|
||||
_addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6");
|
||||
_logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint);
|
||||
|
||||
clientSocket.NoDelay = _options.NoDelay;
|
||||
stream = clientSocket.GetStream();
|
||||
X509Certificate2 clientCertificate = null;
|
||||
|
||||
if (_tlsCertificate != null)
|
||||
var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate();
|
||||
|
||||
if (clientCertificate != null)
|
||||
{
|
||||
if (!clientCertificate.HasPrivateKey)
|
||||
{
|
||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||
}
|
||||
|
||||
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
||||
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
await sslStream.AuthenticateAsServerAsync(
|
||||
new SslServerAuthenticationOptions()
|
||||
new SslServerAuthenticationOptions
|
||||
{
|
||||
ServerCertificate = _tlsCertificate,
|
||||
ServerCertificate = clientCertificate,
|
||||
ClientCertificateRequired = _tlsOptions.ClientCertificateRequired,
|
||||
EnabledSslProtocols = _tlsOptions.SslProtocol,
|
||||
CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
|
||||
@@ -211,7 +206,7 @@ namespace MQTTnet.Implementations
|
||||
}).ConfigureAwait(false);
|
||||
#else
|
||||
await sslStream.AuthenticateAsServerAsync(
|
||||
_tlsCertificate,
|
||||
clientCertificate,
|
||||
_tlsOptions.ClientCertificateRequired,
|
||||
_tlsOptions.SslProtocol,
|
||||
_tlsOptions.CheckCertificateRevocation).ConfigureAwait(false);
|
||||
@@ -254,7 +249,7 @@ namespace MQTTnet.Implementations
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Error(exception, "Error while handling client connection.");
|
||||
_logger.Error(exception, "Error while handling TCP client connection");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -269,10 +264,7 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Verbose("Client '{0}' disconnected at TCP listener '{1}, {2}'.",
|
||||
remoteEndPoint,
|
||||
_localEndPoint,
|
||||
_addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6");
|
||||
_logger.Verbose("TCP client '{0}' disconnected (Local endpoint={1})", remoteEndPoint, _localEndPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace MQTTnet.Internal
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
#if !NETSTANDARD1_3 && !WINDOWS_UWP
|
||||
if (typeof(IDisposable).IsAssignableFrom(typeof(TItem)))
|
||||
{
|
||||
while (_queue.TryDequeue(out TItem item))
|
||||
|
||||
@@ -403,6 +403,18 @@ namespace MQTTnet.Server
|
||||
CreateSubscriptionResult createSubscriptionResult,
|
||||
SubscribeResult subscribeResult)
|
||||
{
|
||||
if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe)
|
||||
{
|
||||
// This is a MQTT V5+ feature.
|
||||
return;
|
||||
}
|
||||
|
||||
if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !createSubscriptionResult.IsNewSubscription)
|
||||
{
|
||||
// This is a MQTT V5+ feature.
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = retainedMessages.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var retainedMessage = retainedMessages[index];
|
||||
@@ -411,18 +423,6 @@ namespace MQTTnet.Server
|
||||
continue;
|
||||
}
|
||||
|
||||
if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe)
|
||||
{
|
||||
// This is a MQTT V5+ feature.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !createSubscriptionResult.IsNewSubscription)
|
||||
{
|
||||
// This is a MQTT V5+ feature.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (MqttTopicFilterComparer.Compare(retainedMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -13,20 +13,6 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public static class MqttServerExtensions
|
||||
{
|
||||
public static Task InjectApplicationMessage(this MqttServer server, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
return server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(applicationMessage)
|
||||
{
|
||||
SenderClientId = string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
public static Task InjectApplicationMessage(
|
||||
this MqttServer server,
|
||||
string topic,
|
||||
@@ -51,13 +37,14 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
return server.InjectApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = topic,
|
||||
Payload = payloadBuffer,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
Retain = retain
|
||||
});
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = topic,
|
||||
Payload = payloadBuffer,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
Retain = retain
|
||||
}));
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters)
|
||||
|
||||
@@ -198,6 +198,18 @@ namespace MQTTnet.Server
|
||||
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider)
|
||||
{
|
||||
if (certificateProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(certificateProvider));
|
||||
}
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = certificateProvider;
|
||||
|
||||
return this;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user