mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 17:00:03 +08:00
Compare commits
57
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 | ||
|
|
2aa4219838 | ||
|
|
b98417ab39 | ||
|
|
ead58dd606 | ||
|
|
a93e935585 | ||
|
|
aeb07e880e | ||
|
|
e0ce1f309e | ||
|
|
d74c81c3d1 | ||
|
|
2fa7200fb3 | ||
|
|
9f7d15e1b1 | ||
|
|
fdf00f1b18 | ||
|
|
abe7bbda15 | ||
|
|
795139d78f | ||
|
|
c7390ef0d7 | ||
|
|
8a926a3db3 | ||
|
|
7d5e49b44c | ||
|
|
555c3eac7a | ||
|
|
f79649d71b | ||
|
|
c9c27b9f01 | ||
|
|
0bc47cf853 | ||
|
|
5649328bc5 | ||
|
|
726dd15c5a | ||
|
|
d513c6f85c | ||
|
|
c1c17ba162 | ||
|
|
f8aa686557 | ||
|
|
d7eb4bbe6e | ||
|
|
c17a350796 | ||
|
|
bb106f8c6e | ||
|
|
09e47fb5eb | ||
|
|
099209fff2 | ||
|
|
d572490a67 | ||
|
|
180e7f79d4 | ||
|
|
d9da7cc92e | ||
|
|
c2ce2044cc | ||
|
|
e400bfb79e | ||
|
|
3d32faa8f9 | ||
|
|
862d76471b | ||
|
|
27f2474876 | ||
|
|
01a5aeeb58 | ||
|
|
47df131e7f | ||
|
|
56750adfe4 | ||
|
|
11e64ef018 | ||
|
|
9b20e0a430 | ||
|
|
41df2ec5da | ||
|
|
e45b207375 | ||
|
|
c37866e534 | ||
|
|
bda4ab7025 | ||
|
|
32cc1ff7b1 | ||
|
|
b5952a590a | ||
|
|
581a024ceb | ||
|
|
c45cb8b04e | ||
|
|
a5e1a4d6dd |
@@ -1 +1,10 @@
|
||||
|
||||
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
|
||||
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
|
||||
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
|
||||
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
|
||||
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
|
||||
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
|
||||
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
|
||||
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
|
||||
* [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!
|
||||
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "4.1.3.${{github.run_number}}"
|
||||
VERSION: "4.2.0.${{github.run_number}}"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
dotnet-version: |
|
||||
3.1.x
|
||||
6.0.x
|
||||
7.0.x
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
@@ -60,8 +61,4 @@ jobs:
|
||||
|
||||
- name: Publish MyGet nugets
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
|
||||
# - name: Publish nugets
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
@@ -240,8 +240,14 @@ See the LICENSE file in the project root for more information.</s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREC/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREL/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=SUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -4,7 +4,7 @@
|
||||
</p>
|
||||
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
||||
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
||||
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
||||

|
||||
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client()
|
||||
{
|
||||
/*
|
||||
@@ -138,7 +160,7 @@ public static class Client_Connection_Samples
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
|
||||
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
|
||||
* encryption algorithms and supports more platforms.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
@@ -232,7 +254,7 @@ public static class Client_Connection_Samples
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +319,7 @@ public static class Client_Connection_Samples
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in METTv5.0.0+.
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
@@ -386,4 +408,4 @@ public static class Client_Connection_Samples
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
|
||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||
|
||||
+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,53 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
public static class Server_Diagnostics_Samples
|
||||
{
|
||||
public static async Task Get_Notified_When_Client_Received_Message()
|
||||
{
|
||||
/*
|
||||
* This sample starts a MQTT server and attaches an event which gets fired whenever
|
||||
* a client received and acknowledged a PUBLISH packet.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,6 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||
|
||||
@@ -18,7 +18,7 @@ using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Tests.Extensions;
|
||||
using MQTTnet.Tests.Helpers;
|
||||
|
||||
namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
@@ -35,7 +35,7 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
connection.Transport = pipe;
|
||||
var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
|
||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
|
||||
|
||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||
|
||||
|
||||
@@ -8,17 +8,15 @@
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\"/>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
}
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public override ValueTask DisposeAsync()
|
||||
#else
|
||||
public Task DisposeAsync()
|
||||
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
|
||||
_socket?.Dispose();
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
return base.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
@@ -32,7 +32,8 @@
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -50,11 +51,11 @@
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -2,38 +2,59 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore.Client.Tcp;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.IO.Pipelines;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
{
|
||||
readonly ConnectionContext _connection;
|
||||
readonly AsyncLock _writerLock = new AsyncLock();
|
||||
|
||||
|
||||
PipeReader _input;
|
||||
PipeWriter _output;
|
||||
|
||||
|
||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||
{
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
|
||||
if (Connection.Transport != null)
|
||||
_input = connection.Transport.Input;
|
||||
_output = connection.Transport.Output;
|
||||
}
|
||||
|
||||
public long BytesReceived { get; private set; }
|
||||
|
||||
public long BytesSent { get; private set; }
|
||||
|
||||
public X509Certificate2 ClientCertificate
|
||||
{
|
||||
get
|
||||
{
|
||||
_input = Connection.Transport.Input;
|
||||
_output = Connection.Transport.Output;
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
return tlsFeature.ClientCertificate;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,47 +62,59 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
if (Connection?.RemoteEndPoint != null)
|
||||
// mqtt over tcp
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
if (_connection.RemoteEndPoint != null)
|
||||
{
|
||||
return Connection.RemoteEndPoint.ToString();
|
||||
return _connection.RemoteEndPoint.ToString();
|
||||
}
|
||||
#endif
|
||||
var connection = Http?.HttpContext?.Connection;
|
||||
if (connection == null)
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.RemoteIpAddress != null)
|
||||
{
|
||||
return Connection.ConnectionId;
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
|
||||
}
|
||||
|
||||
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
|
||||
|
||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
||||
|
||||
public ConnectionContext Connection { get; }
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
public long BytesReceived { get; set; }
|
||||
|
||||
public bool IsReadingPacket { get; private set; }
|
||||
|
||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
||||
public bool IsSecureConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
// mqtt over tcp
|
||||
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||
if (tlsFeature != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||
if (httpFeature?.HttpContext != null)
|
||||
{
|
||||
return httpFeature.HttpContext.Request.IsHttps;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
||||
if (_connection is TcpConnection tcp && !tcp.IsConnected)
|
||||
{
|
||||
await tcp.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_input = Connection.Transport.Input;
|
||||
_output = Connection.Transport.Output;
|
||||
_input = _connection.Transport.Input;
|
||||
_output = _connection.Transport.Output;
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
@@ -92,16 +125,19 @@ namespace MQTTnet.AspNetCore
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_writerLock.Dispose();
|
||||
}
|
||||
|
||||
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var input = Connection.Transport.Input;
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync(cancellationToken);
|
||||
var readTask = _input.ReadAsync(cancellationToken);
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
@@ -141,15 +177,15 @@ namespace MQTTnet.AspNetCore
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
_input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception exception)
|
||||
{
|
||||
// completing the channel makes sure that there is no more data read after a protocol error
|
||||
_input?.Complete(e);
|
||||
_output?.Complete(e);
|
||||
_input?.Complete(exception);
|
||||
_output?.Complete(exception);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
@@ -169,25 +205,44 @@ namespace MQTTnet.AspNetCore
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
var formatter = PacketFormatterAdapter;
|
||||
|
||||
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var buffer = formatter.Encode(packet);
|
||||
var msg = buffer.Join().AsMemory();
|
||||
var output = _output;
|
||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
||||
if (result.IsCompleted)
|
||||
try
|
||||
{
|
||||
BytesSent += msg.Length;
|
||||
}
|
||||
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
PacketFormatterAdapter.Cleanup();
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
// zero copy
|
||||
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
BytesSent += buffer.Length;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PacketFormatterAdapter.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,23 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static class ReaderExtensions
|
||||
{
|
||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
copy = copy.Slice(headerLength);
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = bodySlice.GetMemory().ToArray();
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||
|
||||
var bodySegment = GetArraySegment(ref bodySlice);
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
|
||||
return true;
|
||||
}
|
||||
|
||||
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
|
||||
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment)
|
||||
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||
{
|
||||
return input.First;
|
||||
return segment;
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
return input.ToArray();
|
||||
var array = input.ToArray();
|
||||
return new ArraySegment<byte>(array);
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||
{
|
||||
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
|
||||
if (valueSequence.IsSingleSegment)
|
||||
{
|
||||
var valueSpan =
|
||||
#if NET5_0_OR_GREATER
|
||||
valueSequence.FirstSpan;
|
||||
#else
|
||||
valueSequence.First.Span;
|
||||
#endif
|
||||
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> valueSpan = stackalloc byte[8];
|
||||
valueSequence.CopyTo(valueSpan);
|
||||
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
|
||||
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ReadOnlySpan<byte> span, out int headerLength, out int bodyLength)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 1;
|
||||
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
|
||||
var index = 1;
|
||||
headerLength = 0;
|
||||
bodyLength = 0;
|
||||
|
||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
|
||||
|
||||
do
|
||||
{
|
||||
if (index == temp.Length)
|
||||
if (index == span.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
encodedByte = temp[index];
|
||||
encodedByte = span[index];
|
||||
index++;
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||
ThrowProtocolViolationException(span, index);
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
headerLength = index;
|
||||
bodyLength = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
public abstract class BaseBenchmark
|
||||
|
||||
@@ -2,54 +2,25 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public sealed class ChannelAdapterBenchmark
|
||||
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttChannelAdapter _channelAdapter;
|
||||
int _iterations;
|
||||
MemoryStream _stream;
|
||||
MqttPublishPacket _packet;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
Topic = "A"
|
||||
};
|
||||
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
|
||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||
|
||||
_iterations = 10000;
|
||||
|
||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||
|
||||
for (var i = 0; i < _iterations; i++)
|
||||
{
|
||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
|
||||
var channel = new MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||
}
|
||||
MemoryStream _stream;
|
||||
|
||||
[Benchmark]
|
||||
public void Receive_10000_Messages()
|
||||
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
|
||||
_stream.Position = 0;
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_packet = new MqttPublishPacket
|
||||
{
|
||||
Topic = "A"
|
||||
};
|
||||
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
|
||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||
|
||||
_iterations = 10000;
|
||||
|
||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||
|
||||
for (var i = 0; i < _iterations; i++)
|
||||
{
|
||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||
}
|
||||
|
||||
_stream.Position = 0;
|
||||
|
||||
var channel = new MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
{
|
||||
var buffer = new MemoryStream();
|
||||
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
|
||||
return buffer.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Validators;
|
||||
|
||||
namespace MQTTnet.Benchmarks.Configurations
|
||||
{
|
||||
/// <summary>
|
||||
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
|
||||
/// https://benchmarkdotnet.org/Configs/Configs.htm
|
||||
/// </summary>
|
||||
public class AllowNonOptimized : BaseConfig
|
||||
{
|
||||
public AllowNonOptimized()
|
||||
{
|
||||
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
|
||||
AddJob(Job.InProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Configs;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet.Benchmarks.Configurations
|
||||
{
|
||||
public class BaseConfig : ManualConfig
|
||||
{
|
||||
public BaseConfig()
|
||||
{
|
||||
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
|
||||
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
|
||||
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Environments;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Toolchains.CsProj;
|
||||
|
||||
namespace MQTTnet.Benchmarks.Configurations
|
||||
{
|
||||
public class RuntimeCompareConfig : BaseConfig
|
||||
{
|
||||
public RuntimeCompareConfig()
|
||||
{
|
||||
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
|
||||
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class LoggerBenchmark
|
||||
public class LoggerBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttNetNullLogger _nullLogger;
|
||||
MqttNetSourceLogger _sourceNullLogger;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,11 +26,12 @@
|
||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System;
|
||||
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MemoryCopyBenchmark
|
||||
{
|
||||
const int max_length = 1024 * 8;
|
||||
private byte[] source;
|
||||
private byte[] target;
|
||||
|
||||
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||
public int Length { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
source = new byte[max_length];
|
||||
target = new byte[max_length];
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Array_Copy()
|
||||
{
|
||||
Array.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Memory_Copy()
|
||||
{
|
||||
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
@@ -8,14 +11,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a number of topics, publish, subscribe, and wait for response
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
public class MessageDeliveryBenchmark
|
||||
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||
{
|
||||
List<MqttApplicationMessage> _topicPublishMessages;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingBenchmark
|
||||
public class MessageProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MessageProcessingMqttConnectionContextBenchmark
|
||||
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||
{
|
||||
IWebHost _host;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttBufferReaderBenchmark
|
||||
{
|
||||
byte[] _buffer;
|
||||
int _bufferLength;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
var writer = new MqttBufferWriter(1024, 1024);
|
||||
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||
|
||||
_buffer = writer.GetBuffer();
|
||||
_bufferLength = writer.Length;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Span()
|
||||
{
|
||||
var span = _buffer.AsSpan(0, _bufferLength);
|
||||
Encoding.UTF8.GetString(span);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Use_Encoding()
|
||||
{
|
||||
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class MqttPacketReaderWriterBenchmark
|
||||
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||
{
|
||||
readonly byte[] _demoPayload = new byte[1024];
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public sealed class MqttTcpChannelBenchmark
|
||||
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttChannel _serverChannel;
|
||||
|
||||
@@ -3,85 +3,140 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Running;
|
||||
using MQTTnet.Benchmarks.Configurations;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
||||
Console.WriteLine("--------------------------------------------------------");
|
||||
Console.WriteLine("1 = MessageProcessingBenchmark");
|
||||
Console.WriteLine("2 = SerializerBenchmark");
|
||||
Console.WriteLine("3 = LoggerBenchmark");
|
||||
Console.WriteLine("4 = TopicFilterComparerBenchmark");
|
||||
Console.WriteLine("5 = ChannelAdapterBenchmark");
|
||||
Console.WriteLine("6 = MqttTcpChannelBenchmark");
|
||||
Console.WriteLine("7 = TcpPipesBenchmark");
|
||||
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
|
||||
Console.WriteLine("9 = ServerProcessingBenchmark");
|
||||
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
|
||||
Console.WriteLine("b = RoundtripBenchmark");
|
||||
Console.WriteLine("c = SubscribeBenchmark");
|
||||
Console.WriteLine("d = UnsubscribeBenchmark");
|
||||
Console.WriteLine("e = MessageDeliveryBenchmark");
|
||||
Console.WriteLine("f = AsyncLockBenchmark");
|
||||
static int _selectedBenchmarkIndex;
|
||||
static List<Type> _benchmarks;
|
||||
|
||||
var pressedKey = Console.ReadKey(true);
|
||||
switch (pressedKey.KeyChar)
|
||||
public static void Main(string[] arguments)
|
||||
{
|
||||
_benchmarks = CollectBenchmarks();
|
||||
HandleArguments(arguments);
|
||||
|
||||
while (true)
|
||||
{
|
||||
case '1':
|
||||
BenchmarkRunner.Run<MessageProcessingBenchmark>();
|
||||
break;
|
||||
case '2':
|
||||
BenchmarkRunner.Run<SerializerBenchmark>();
|
||||
break;
|
||||
case '3':
|
||||
BenchmarkRunner.Run<LoggerBenchmark>();
|
||||
break;
|
||||
case '4':
|
||||
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
|
||||
break;
|
||||
case '5':
|
||||
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
|
||||
break;
|
||||
case '6':
|
||||
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
|
||||
break;
|
||||
case '7':
|
||||
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
||||
break;
|
||||
case '8':
|
||||
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
|
||||
break;
|
||||
case '9':
|
||||
BenchmarkRunner.Run<ServerProcessingBenchmark>();
|
||||
break;
|
||||
case 'a':
|
||||
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
|
||||
break;
|
||||
case 'b':
|
||||
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
|
||||
break;
|
||||
case 'c':
|
||||
BenchmarkRunner.Run<SubscribeBenchmark>();
|
||||
break;
|
||||
case 'd':
|
||||
BenchmarkRunner.Run<UnsubscribeBenchmark>();
|
||||
break;
|
||||
case 'e':
|
||||
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
|
||||
break;
|
||||
case 'f':
|
||||
BenchmarkRunner.Run<AsyncLockBenchmark>();
|
||||
RenderMenu();
|
||||
|
||||
var key = Console.ReadKey(true);
|
||||
|
||||
if (key.Key == ConsoleKey.DownArrow)
|
||||
{
|
||||
_selectedBenchmarkIndex++;
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
}
|
||||
else if (key.Key == ConsoleKey.UpArrow)
|
||||
{
|
||||
_selectedBenchmarkIndex--;
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
else if (key.Key == ConsoleKey.Escape)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else if (key.Key == ConsoleKey.Enter)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press any key to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static List<Type> CollectBenchmarks()
|
||||
{
|
||||
var benchmarks = new List<Type>();
|
||||
|
||||
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||
{
|
||||
if (type != typeof(BaseBenchmark))
|
||||
{
|
||||
benchmarks.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||
}
|
||||
|
||||
static void HandleArguments(string[] arguments)
|
||||
{
|
||||
if (arguments.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow for preselection to avoid developer frustration.
|
||||
|
||||
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||
{
|
||||
_selectedBenchmarkIndex = benchmarkIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
|
||||
|
||||
if (_selectedBenchmarkIndex < 0)
|
||||
{
|
||||
_selectedBenchmarkIndex = 0;
|
||||
}
|
||||
|
||||
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||
{
|
||||
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderMenu()
|
||||
{
|
||||
Console.Clear();
|
||||
|
||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
||||
Console.WriteLine("-----------------------------------------------");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||
Console.WriteLine("Press Enter for benchmark execution");
|
||||
Console.WriteLine("Press Esc for exit");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
|
||||
for (var index = 0; index < _benchmarks.Count; index++)
|
||||
{
|
||||
var benchmark = _benchmarks[index];
|
||||
|
||||
if (_selectedBenchmarkIndex == index)
|
||||
{
|
||||
Console.Write("-> ");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine(benchmark.Name);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.AspNetCore;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ReaderExtensionsBenchmark
|
||||
{
|
||||
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||
MemoryStream stream;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("topic")
|
||||
.WithPayload(new byte[10 * 1024])
|
||||
.Build();
|
||||
|
||||
var packet = MqttPacketFactories.Publish.Create(mqttMessage);
|
||||
|
||||
var buffer = mqttPacketFormatter.Encode(packet);
|
||||
stream = new MemoryStream();
|
||||
stream.Write(buffer.Packet);
|
||||
stream.Write(buffer.Payload);
|
||||
mqttPacketFormatter.Cleanup();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var input = PipeReader.Create(stream);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ReadResult readResult;
|
||||
var readTask = input.ReadAsync();
|
||||
if (readTask.IsCompleted)
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var buffer = readResult.Buffer;
|
||||
|
||||
var consumed = buffer.Start;
|
||||
var observed = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!buffer.IsEmpty)
|
||||
{
|
||||
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (readResult.IsCompleted)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection Aborted");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||
// before yielding the read again.
|
||||
input.AdvanceTo(consumed, observed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReaderExtensions_Before
|
||||
{
|
||||
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
|
||||
in ReadOnlySequence<byte> input,
|
||||
out MqttPacket packet,
|
||||
out SequencePosition consumed,
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
observed = input.End;
|
||||
bytesRead = 0;
|
||||
var copy = input;
|
||||
|
||||
if (copy.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fixedHeader = copy.First.Span[0];
|
||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copy.Length < bodyLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodySlice = copy.Slice(0, bodyLength);
|
||||
var buffer = GetMemory(bodySlice).ToArray();
|
||||
|
||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||
|
||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||
}
|
||||
|
||||
packet = formatter.Decode(receivedMqttPacket);
|
||||
consumed = bodySlice.End;
|
||||
observed = bodySlice.End;
|
||||
bytesRead = headerLength + bodyLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||
{
|
||||
if (input.IsSingleSegment)
|
||||
{
|
||||
return input.First;
|
||||
}
|
||||
|
||||
// Should be rare
|
||||
return input.ToArray();
|
||||
}
|
||||
|
||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||
{
|
||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||
var multiplier = 1;
|
||||
var value = 0;
|
||||
byte encodedByte;
|
||||
var index = 1;
|
||||
headerLength = 0;
|
||||
bodyLength = 0;
|
||||
|
||||
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||
|
||||
do
|
||||
{
|
||||
if (index == temp.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
encodedByte = temp[index];
|
||||
index++;
|
||||
|
||||
value += (byte)(encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||
}
|
||||
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
input = input.Slice(index);
|
||||
|
||||
headerLength = index;
|
||||
bodyLength = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class RoundtripProcessingBenchmark
|
||||
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||
{
|
||||
MemoryStream stream;
|
||||
MqttPacketBuffer buffer;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
buffer = new MqttPacketBuffer(packet);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class ServerProcessingBenchmark
|
||||
public class ServerProcessingBenchmark : BaseBenchmark
|
||||
{
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Client;
|
||||
@@ -8,7 +12,7 @@ using System.Linq;
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SubscribeBenchmark
|
||||
public class SubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[MemoryDiagnoser]
|
||||
public class TcpPipesBenchmark
|
||||
public class TcpPipesBenchmark : BaseBenchmark
|
||||
{
|
||||
IDuplexPipe _client;
|
||||
IDuplexPipe _server;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter]
|
||||
[MemoryDiagnoser]
|
||||
public class TopicFilterComparerBenchmark
|
||||
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||
{
|
||||
static readonly char[] TopicLevelSeparator = { '/' };
|
||||
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
public class TopicGenerator
|
||||
{
|
||||
public static void Generate(
|
||||
int numPublishers, int numTopicsPerPublisher,
|
||||
out Dictionary<string, List<string>> topicsByPublisher,
|
||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
|
||||
)
|
||||
int numPublishers,
|
||||
int numTopicsPerPublisher,
|
||||
out Dictionary<string, List<string>> topicsByPublisher,
|
||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||
{
|
||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||
|
||||
// Find some reasonable distribution across three topic levels
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
|
||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||
if (topicsPerLevel <= 0)
|
||||
{
|
||||
topicsPerLevel = 1;
|
||||
}
|
||||
|
||||
int numLevel1Topics = topicsPerLevel;
|
||||
int numLevel2Topics = topicsPerLevel;
|
||||
var numLevel1Topics = topicsPerLevel;
|
||||
var numLevel2Topics = topicsPerLevel;
|
||||
|
||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||
if (maxNumLevel3Topics <= 0)
|
||||
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
for (var p = 0; p < numPublishers; ++p)
|
||||
{
|
||||
int publisherTopicCount = 0;
|
||||
var publisherTopicCount = 0;
|
||||
var publisherName = "pub" + p;
|
||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||
{
|
||||
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
|
||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||
{
|
||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
|
||||
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||
|
||||
if (l2 == 0)
|
||||
{
|
||||
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
|
||||
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||
}
|
||||
if ((l1 == 0) && (l3 == 0))
|
||||
|
||||
if (l1 == 0 && l3 == 0)
|
||||
{
|
||||
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
|
||||
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||
}
|
||||
|
||||
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
|
||||
topicList = new List<string>();
|
||||
topicsByPublisher.Add(publisherName, topicList);
|
||||
}
|
||||
|
||||
topicList.Add(topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Server;
|
||||
@@ -7,7 +11,7 @@ using System.Linq;
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class UnsubscribeBenchmark
|
||||
public class UnsubscribeBenchmark : BaseBenchmark
|
||||
{
|
||||
MqttServer _mqttServer;
|
||||
IMqttClient _mqttClient;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
|
||||
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
|
||||
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
|
||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
|
||||
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
|
||||
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
|
||||
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
|
||||
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
|
||||
<package id="System.Console" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
|
||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
|
||||
</packages>
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
||||
{
|
||||
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
|
||||
{
|
||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||
});
|
||||
return factory.CreateMqttRpcClient(
|
||||
mqttClient,
|
||||
new MqttRpcClientOptions
|
||||
{
|
||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||
});
|
||||
}
|
||||
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||
|
||||
@@ -2,23 +2,25 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Protocol;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public sealed class MqttRpcClient : IMqttRpcClient
|
||||
{
|
||||
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
@@ -27,6 +29,18 @@ namespace MQTTnet.Extensions.Rpc
|
||||
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
}
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||
@@ -49,15 +63,13 @@ namespace MQTTnet.Extensions.Rpc
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
|
||||
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
|
||||
if (methodName == null)
|
||||
{
|
||||
MethodName = methodName,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
MqttClient = _mqttClient,
|
||||
Options = _options
|
||||
});
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||
|
||||
var requestTopic = topicNames.RequestTopic;
|
||||
var responseTopic = topicNames.ResponseTopic;
|
||||
@@ -72,34 +84,34 @@ namespace MQTTnet.Extensions.Rpc
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithResponseTopic(responseTopic)
|
||||
.Build();
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
#if NET452
|
||||
var awaitable = new TaskCompletionSource<byte[]>();
|
||||
#else
|
||||
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
#endif
|
||||
var awaitable = new AsyncTaskCompletionSource<byte[]>();
|
||||
|
||||
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
||||
.WithTopicFilter(responseTopic, qualityOfServiceLevel)
|
||||
.Build();
|
||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
|
||||
|
||||
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
|
||||
using (cancellationToken.Register(
|
||||
() =>
|
||||
{
|
||||
awaitable.TrySetCanceled();
|
||||
}))
|
||||
{
|
||||
return await awaitable.Task.ConfigureAwait(false);
|
||||
}
|
||||
@@ -107,8 +119,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
|
||||
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,28 +131,12 @@ namespace MQTTnet.Extensions.Rpc
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
#if NET452
|
||||
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
|
||||
#else
|
||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||
#endif
|
||||
|
||||
// Set this message to handled to that other code can avoid execution etc.
|
||||
eventArgs.IsHandled = true;
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||
|
||||
foreach (var tcs in _waitingCalls)
|
||||
{
|
||||
tcs.Value.TrySetCanceled();
|
||||
}
|
||||
|
||||
_waitingCalls.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||
|
||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||
{
|
||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttRpcClientOptions Build()
|
||||
{
|
||||
return new MqttRpcClientOptions
|
||||
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
|
||||
TopicGenerationStrategy = _topicGenerationStrategy
|
||||
};
|
||||
}
|
||||
|
||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||
{
|
||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||
{
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -9,20 +10,29 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public sealed class TopicGenerationContext
|
||||
{
|
||||
public string MethodName { get; set; }
|
||||
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||
{
|
||||
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public string MethodName { get; }
|
||||
|
||||
public IMqttClient MqttClient { get; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the quality of service level.
|
||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
|
||||
/// There are 3 QoS levels in MQTT:
|
||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||
/// Gets or sets the quality of service level.
|
||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||
/// that defines the guarantee of delivery for a specific message.
|
||||
/// There are 3 QoS levels in MQTT:
|
||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||
/// </summary>
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
|
||||
public IMqttClient MqttClient { get; set; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; set; }
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||
|
||||
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
|
||||
{
|
||||
if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
|
||||
if (mqttFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mqttFactory));
|
||||
}
|
||||
|
||||
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
@@ -17,14 +18,14 @@ using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using SuperSocket.ClientEngine;
|
||||
using WebSocket4Net;
|
||||
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
|
||||
|
||||
namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
public sealed class WebSocket4NetMqttChannel : IMqttChannel
|
||||
{
|
||||
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||
|
||||
readonly MqttClientOptions _clientOptions;
|
||||
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||
readonly MqttClientWebSocketOptions _webSocketOptions;
|
||||
|
||||
WebSocket _webSocket;
|
||||
@@ -35,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
|
||||
}
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; }
|
||||
|
||||
public string Endpoint => _webSocketOptions.Uri;
|
||||
|
||||
public bool IsSecureConnection { get; private set; }
|
||||
|
||||
public X509Certificate2 ClientCertificate { get; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var uri = _webSocketOptions.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -55,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
uri = "wss://" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#if NET48 || NETCOREAPP3_0_OR_GREATER
|
||||
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
#endif
|
||||
|
||||
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
||||
@@ -90,7 +93,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
var receiveBufferSize = 0;
|
||||
|
||||
var certificates = new X509CertificateCollection();
|
||||
if (_webSocketOptions?.TlsOptions?.Certificates != null)
|
||||
if (_webSocketOptions.TlsOptions?.Certificates != null)
|
||||
{
|
||||
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
||||
{
|
||||
@@ -107,15 +110,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
NoDelay = true,
|
||||
Security =
|
||||
{
|
||||
AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
|
||||
AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
|
||||
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
|
||||
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
|
||||
Certificates = certificates
|
||||
}
|
||||
};
|
||||
|
||||
await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||
return ConnectInternalAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
@@ -124,10 +125,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
_webSocket.Close();
|
||||
}
|
||||
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_webSocket.DataReceived -= OnDataReceived;
|
||||
_webSocket.Error -= OnError;
|
||||
_webSocket.Dispose();
|
||||
_webSocket = null;
|
||||
}
|
||||
|
||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var readBytes = 0;
|
||||
@@ -161,75 +175,55 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_webSocket.DataReceived -= OnDataReceived;
|
||||
_webSocket.Error -= OnError;
|
||||
_webSocket.Dispose();
|
||||
_webSocket = null;
|
||||
}
|
||||
|
||||
static void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
System.Diagnostics.Debug.Write(e.Exception.ToString());
|
||||
}
|
||||
|
||||
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
foreach (var @byte in e.Data)
|
||||
{
|
||||
_receiveBuffer.Add(@byte);
|
||||
}
|
||||
}
|
||||
|
||||
async Task ConnectInternalAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_webSocket.Error += OnError;
|
||||
_webSocket.DataReceived += OnDataReceived;
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<Exception>();
|
||||
var promise = new AsyncTaskCompletionSource<bool>();
|
||||
|
||||
void ErrorHandler(object sender, ErrorEventArgs e)
|
||||
{
|
||||
taskCompletionSource.TrySetResult(e.Exception);
|
||||
promise.TrySetException(e.Exception);
|
||||
}
|
||||
|
||||
void SuccessHandler(object sender, EventArgs e)
|
||||
void OpenedHandler(object sender, EventArgs e)
|
||||
{
|
||||
taskCompletionSource.TrySetResult(null);
|
||||
promise.TrySetResult(true);
|
||||
}
|
||||
|
||||
void ClosedHandler(object sender, EventArgs e)
|
||||
{
|
||||
promise.TrySetException(new MqttCommunicationException("Connection closed."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_webSocket.Opened += SuccessHandler;
|
||||
_webSocket.Opened += OpenedHandler;
|
||||
_webSocket.Closed += ClosedHandler;
|
||||
_webSocket.Error += ErrorHandler;
|
||||
|
||||
#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
|
||||
_webSocket.Open();
|
||||
#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
|
||||
|
||||
using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
||||
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
|
||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
|
||||
{
|
||||
using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
|
||||
using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
|
||||
{
|
||||
try
|
||||
{
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
// OpenAsync does basically the same but without support for a cancellation token.
|
||||
// So we do it on our own!
|
||||
_webSocket.Open();
|
||||
|
||||
await promise.Task.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||
if (timeoutReached)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException(exception);
|
||||
}
|
||||
|
||||
|
||||
if (exception is AuthenticationException authenticationException)
|
||||
{
|
||||
throw new MqttCommunicationException(authenticationException.InnerException);
|
||||
@@ -251,9 +245,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
}
|
||||
finally
|
||||
{
|
||||
_webSocket.Opened -= SuccessHandler;
|
||||
_webSocket.Opened -= OpenedHandler;
|
||||
_webSocket.Closed -= ClosedHandler;
|
||||
_webSocket.Error -= ErrorHandler;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
foreach (var @byte in e.Data)
|
||||
{
|
||||
_receiveBuffer.Add(@byte);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
Debug.Write(e.Exception.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using System;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
switch (options.ChannelOptions)
|
||||
{
|
||||
@@ -23,7 +26,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
return new MqttChannelAdapter(
|
||||
new MqttTcpChannel(options),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
}
|
||||
@@ -32,16 +35,16 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
{
|
||||
return new MqttChannelAdapter(
|
||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Client;
|
||||
using System;
|
||||
using System.Security.Authentication;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.WebSocket4Net;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -17,10 +18,6 @@ namespace MQTTnet.TestApp
|
||||
{
|
||||
public static async Task RunAsync()
|
||||
{
|
||||
// MqttNetConsoleLogger.ForwardToConsole();
|
||||
|
||||
// For most of these connections to work, set output target to Net5.0.
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
// TLS13 is only available in Net5.0
|
||||
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
||||
@@ -31,7 +28,6 @@ namespace MQTTnet.TestApp
|
||||
CertificateValidationHandler = w => true
|
||||
};
|
||||
#endif
|
||||
|
||||
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
||||
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
||||
{
|
||||
@@ -42,33 +38,37 @@ namespace MQTTnet.TestApp
|
||||
};
|
||||
|
||||
// mqtt.eclipseprojects.io
|
||||
await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"mqtt.eclipseprojects.io TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"mqtt.eclipseprojects.io WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||
|
||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
|
||||
true);
|
||||
#endif
|
||||
|
||||
// test.mosquitto.org
|
||||
await ExecuteTestAsync("test.mosquitto.org TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
|
||||
.WithCredentials("rw", "readwrite")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org TCP - Authenticated",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org TCP TLS12",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
||||
@@ -76,27 +76,40 @@ namespace MQTTnet.TestApp
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||
#endif
|
||||
|
||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org TCP TLS12 - Authenticated",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
||||
.WithCredentials("rw", "readwrite")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||
.WithTls(unsafeTls12)
|
||||
.Build());
|
||||
|
||||
await ExecuteTestAsync("test.mosquitto.org WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("test.mosquitto.org WS TLS12",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org WS (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||
true);
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org WS TLS12",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
|
||||
// await ExecuteTestAsync(
|
||||
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
||||
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||
// true);
|
||||
|
||||
// broker.emqx.io
|
||||
await ExecuteTestAsync("broker.emqx.io TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"broker.emqx.io TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("broker.emqx.io TCP TLS12",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
await ExecuteTestAsync(
|
||||
"broker.emqx.io TCP TLS12",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
||||
@@ -104,23 +117,37 @@ namespace MQTTnet.TestApp
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||
#endif
|
||||
|
||||
await ExecuteTestAsync("broker.emqx.io WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"broker.emqx.io WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("broker.emqx.io WS TLS12",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
await ExecuteTestAsync(
|
||||
"broker.emqx.io WS (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||
true);
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"broker.emqx.io WS TLS12",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||
true);
|
||||
|
||||
// broker.hivemq.com
|
||||
await ExecuteTestAsync("broker.hivemq.com TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"broker.hivemq.com TCP",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync("broker.hivemq.com WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
await ExecuteTestAsync(
|
||||
"broker.hivemq.com WS",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"broker.hivemq.com WS (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||
true);
|
||||
|
||||
// mqtt.swifitch.cz: Does not seem to operate any more
|
||||
|
||||
@@ -130,36 +157,43 @@ namespace MQTTnet.TestApp
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
static async Task ExecuteTestAsync(string name, MqttClientOptions options)
|
||||
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
|
||||
|
||||
var factory = new MqttFactory();
|
||||
//factory.UseWebSocket4Net();
|
||||
var client = factory.CreateMqttClient();
|
||||
var topic = Guid.NewGuid().ToString();
|
||||
|
||||
MqttApplicationMessage receivedMessage = null;
|
||||
client.ApplicationMessageReceivedAsync += e =>
|
||||
if (useWebSocket4Net)
|
||||
{
|
||||
receivedMessage = e.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await client.ConnectAsync(options);
|
||||
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
||||
|
||||
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
||||
{
|
||||
throw new Exception("Message invalid.");
|
||||
factory.UseWebSocket4Net();
|
||||
}
|
||||
|
||||
await client.UnsubscribeAsync(topic);
|
||||
await client.DisconnectAsync();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
MqttApplicationMessage receivedMessage = null;
|
||||
client.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
receivedMessage = e.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||
|
||||
var topic = Guid.NewGuid().ToString();
|
||||
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
|
||||
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
|
||||
|
||||
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
||||
|
||||
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
||||
{
|
||||
throw new Exception("Message invalid.");
|
||||
}
|
||||
|
||||
await client.UnsubscribeAsync(topic).ConfigureAwait(false);
|
||||
await client.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Write("[OK]\n", ConsoleColor.Green);
|
||||
}
|
||||
@@ -174,6 +208,5 @@ namespace MQTTnet.TestApp
|
||||
Console.ForegroundColor = color;
|
||||
Console.Write(message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class LowLevelMqttClient_Tests : BaseTestClass
|
||||
+13
-21
@@ -17,7 +17,7 @@ using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||
@@ -47,10 +47,12 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await managedClient.StartAsync(managedClientOptions);
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
// Send test data.
|
||||
await senderClient.PublishStringAsync("topic1");
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
|
||||
@@ -162,7 +164,7 @@ namespace MQTTnet.Tests.Client
|
||||
.Build();
|
||||
|
||||
var dyingClient = testEnvironment.CreateClient();
|
||||
var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
|
||||
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
@@ -197,7 +199,7 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
@@ -224,7 +226,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
var storage = new ManagedMqttClientTestStorage();
|
||||
|
||||
@@ -329,13 +331,7 @@ namespace MQTTnet.Tests.Client
|
||||
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
|
||||
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||
await sendingClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "topic",
|
||||
Payload = new byte[] { 1 },
|
||||
Retain = true
|
||||
});
|
||||
await sendingClient.PublishStringAsync("topic", "A", retain: true);
|
||||
|
||||
// Wait a bit for the retained message to be available
|
||||
await LongTestDelay();
|
||||
@@ -357,18 +353,15 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, receivedManagedMessages.Count);
|
||||
|
||||
await managedClient.StopAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
|
||||
|
||||
await managedClient.StartAsync(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,
|
||||
@@ -377,7 +370,6 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
// Make sure that it gets received after subscribing again.
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(2, receivedManagedMessages.Count);
|
||||
@@ -447,7 +439,7 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ManagedMqttClient> CreateManagedClientAsync(
|
||||
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
|
||||
TestEnvironment testEnvironment,
|
||||
IMqttClient underlyingClient = null,
|
||||
TimeSpan? connectionCheckInterval = null,
|
||||
@@ -464,7 +456,7 @@ namespace MQTTnet.Tests.Client
|
||||
// at connection check intervals
|
||||
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
|
||||
|
||||
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
@@ -481,7 +473,7 @@ namespace MQTTnet.Tests.Client
|
||||
/// name="managedClient" />
|
||||
/// has connected
|
||||
/// </summary>
|
||||
Task GetConnectedTask(ManagedMqttClient managedClient)
|
||||
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
|
||||
{
|
||||
var connected = new TaskCompletionSource<bool>();
|
||||
|
||||
@@ -503,7 +495,7 @@ namespace MQTTnet.Tests.Client
|
||||
/// name="expectedNumberOfMessages" />
|
||||
/// have been received
|
||||
/// </summary>
|
||||
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
{
|
||||
var receivedMessages = new List<MqttApplicationMessage>();
|
||||
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
|
||||
@@ -0,0 +1,162 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Connection_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
||||
|
||||
Assert.Fail("Must fail!");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_Custom_Reason()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_User_Properties()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.IsNotNull(eventArgs.UserProperties);
|
||||
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
||||
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
|
||||
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-46
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Tests : BaseTestClass
|
||||
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
public async Task Connect_Disconnect_Connect()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.DisconnectAsync();
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public async Task Connect_Multiple_Times_Should_Fail()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
Assert.Fail("Must fail!");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
|
||||
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -6,9 +6,8 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttClientOptionsBuilder_Tests
|
||||
@@ -6,68 +6,93 @@ using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Extensions.Rpc;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
namespace MQTTnet.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class RPC_Tests : BaseTestClass
|
||||
public sealed class Rpc_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
var responseSender = await testEnvironment.ConnectClient();
|
||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
|
||||
|
||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
||||
};
|
||||
|
||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||
|
||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||
{
|
||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
|
||||
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_0()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_1()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_2()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_0_MQTT_V5()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_1_MQTT_V5()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_2_MQTT_V5()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
|
||||
{
|
||||
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_1()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_1_MQTT_V5()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
|
||||
{
|
||||
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_2()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_2_MQTT_V5()
|
||||
{
|
||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
|
||||
{
|
||||
@@ -90,6 +115,31 @@ namespace MQTTnet.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
var responseSender = await testEnvironment.ConnectClient();
|
||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
|
||||
|
||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||
await CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||
|
||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||
{
|
||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
public async Task Execute_With_Custom_Topic_Names()
|
||||
@@ -98,14 +148,24 @@ namespace MQTTnet.Tests
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var requestSender = await testEnvironment.ConnectClient();
|
||||
|
||||
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
|
||||
|
||||
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Use_Factory()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var rpcClient = factory.CreateMqttRpcClient(client);
|
||||
|
||||
Assert.IsNotNull(rpcClient);
|
||||
}
|
||||
}
|
||||
|
||||
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
@@ -151,57 +211,6 @@ namespace MQTTnet.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
var responseSender = await testEnvironment.ConnectClient();
|
||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
|
||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
||||
};
|
||||
|
||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||
|
||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||
{
|
||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
|
||||
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
|
||||
{
|
||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
var responseSender = await testEnvironment.ConnectClient();
|
||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
|
||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||
await CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||
|
||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||
{
|
||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||
{
|
||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||
@@ -214,4 +223,4 @@ namespace MQTTnet.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Extensions.WebSocket4Net;
|
||||
using MQTTnet.Tests.Helpers;
|
||||
|
||||
namespace MQTTnet.Tests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class WebSocket4Net_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_Failed_With_Invalid_Server()
|
||||
{
|
||||
var factory = new MqttFactory().UseWebSocket4Net();
|
||||
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Use_Correct_Adapter()
|
||||
{
|
||||
// Should not throw exception
|
||||
var factory = new MqttFactory().UseWebSocket4Net();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var adapterFactory = client.GetFieldValue("_adapterFactory");
|
||||
|
||||
Assert.IsInstanceOfType(adapterFactory, typeof(WebSocket4NetMqttClientAdapterFactory));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Use_WebSocket4Net()
|
||||
{
|
||||
// Should not throw exception
|
||||
new MqttFactory().UseWebSocket4Net();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests.Formatter
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttBufferReader_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
|
||||
public void Fire_Exception_If_Not_Enough_Data()
|
||||
{
|
||||
var buffer = new byte[] { 0, 1, 2 };
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
|
||||
reader.SetBuffer(buffer, 0, 3);
|
||||
|
||||
// 1 byte is missing.
|
||||
reader.ReadFourByteInteger();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
|
||||
public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer()
|
||||
{
|
||||
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
|
||||
reader.SetBuffer(buffer, 0, 3);
|
||||
|
||||
// 1 byte is missing.
|
||||
reader.ReadFourByteInteger();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Is_EndOfStream_Without_Buffer()
|
||||
{
|
||||
var reader = new MqttBufferReader();
|
||||
|
||||
Assert.IsTrue(reader.EndOfStream);
|
||||
Assert.AreEqual(0, reader.BytesLeft);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Read_Remaining_Data_From_Larger_Buffer()
|
||||
{
|
||||
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
|
||||
// The used buffer contains more data than used!
|
||||
reader.SetBuffer(buffer, 0, 5);
|
||||
|
||||
// This should only read 5 bytes even if more data is in the buffer
|
||||
// due to custom bounds.
|
||||
var remainingData = reader.ReadRemainingData();
|
||||
|
||||
Assert.IsTrue(reader.EndOfStream);
|
||||
Assert.AreEqual(0, reader.BytesLeft);
|
||||
Assert.AreEqual(5, remainingData.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Read_Various_Positions_and_Offsets()
|
||||
{
|
||||
const int NumBufferElements = 1000;
|
||||
const int NumTestSequences = 10000;
|
||||
|
||||
const string TestString = "The quick brown fox jumps over the lazy dog.";
|
||||
|
||||
var rndSeed = Environment.TickCount;
|
||||
// rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
|
||||
var rnd = new Random(rndSeed);
|
||||
// rndSeed is printed when an assert fails
|
||||
|
||||
var bufferElements = new List<ElementReference>();
|
||||
|
||||
byte[] elementBuffer;
|
||||
|
||||
// Fill a buffer with strings or numbers in big endian format
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var bw = new BinaryWriter(ms))
|
||||
{
|
||||
var bufferOffset = 0;
|
||||
|
||||
for (var i = 0; i < NumBufferElements; i++)
|
||||
{
|
||||
var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
|
||||
|
||||
byte[] elementBytes = null;
|
||||
uint elementNumberValue = 0; // all element number values fit into uint
|
||||
string elementStringValue = null;
|
||||
var elementSize = 0;
|
||||
|
||||
var alreadyBigEndian = false;
|
||||
switch (nextElementType)
|
||||
{
|
||||
case ElementReference.BufferElementType.Byte:
|
||||
var byteValue = (byte)i;
|
||||
elementNumberValue = byteValue;
|
||||
elementSize = sizeof(byte);
|
||||
elementBytes = new byte[1] { byteValue };
|
||||
alreadyBigEndian = true; // nothing to swap
|
||||
break;
|
||||
case ElementReference.BufferElementType.TwoByteInt:
|
||||
var ushortValue = (ushort)i;
|
||||
elementNumberValue = ushortValue;
|
||||
elementSize = sizeof(ushort);
|
||||
elementBytes = BitConverter.GetBytes(ushortValue);
|
||||
break;
|
||||
case ElementReference.BufferElementType.FourByteInt:
|
||||
var uintValue = (uint)i;
|
||||
elementNumberValue = uintValue;
|
||||
elementSize = sizeof(uint);
|
||||
elementBytes = BitConverter.GetBytes(uintValue);
|
||||
break;
|
||||
case ElementReference.BufferElementType.VariableSizeInt:
|
||||
{
|
||||
elementNumberValue = (uint)i;
|
||||
var writer = new MqttBufferWriter(4, 4);
|
||||
writer.WriteVariableByteInteger(elementNumberValue);
|
||||
elementSize = writer.Length;
|
||||
elementBytes = new byte[elementSize];
|
||||
var buffer = writer.GetBuffer();
|
||||
Array.Copy(buffer, elementBytes, elementSize);
|
||||
alreadyBigEndian = true; // nothing to swap
|
||||
}
|
||||
break;
|
||||
case ElementReference.BufferElementType.String:
|
||||
{
|
||||
var stringLen = rnd.Next(TestString.Length);
|
||||
elementStringValue = TestString.Substring(0, stringLen); // could be empty
|
||||
var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
|
||||
writer.WriteString(elementStringValue);
|
||||
elementSize = writer.Length;
|
||||
elementBytes = new byte[elementSize];
|
||||
var buffer = writer.GetBuffer();
|
||||
Array.Copy(buffer, elementBytes, elementSize);
|
||||
alreadyBigEndian = true; // nothing to swap
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian && !alreadyBigEndian)
|
||||
{
|
||||
Array.Reverse(elementBytes);
|
||||
}
|
||||
|
||||
bw.Write(elementBytes);
|
||||
bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
|
||||
bufferOffset += elementSize;
|
||||
}
|
||||
|
||||
elementBuffer = ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < NumTestSequences; i++)
|
||||
{
|
||||
var firstElementIndex = rnd.Next(NumBufferElements - 1);
|
||||
// segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
|
||||
var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
|
||||
var lastElementIndex = firstElementIndex + elementCount - 1;
|
||||
// Use element index to get buffer offsets
|
||||
var lastElement = bufferElements[lastElementIndex];
|
||||
var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
|
||||
var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
|
||||
var segmentLength = segmentEndPosition - segmentStartPosition;
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
|
||||
|
||||
// read all elements in the buffer segment; values should be as expected
|
||||
for (var n = 0; n < elementCount; n++)
|
||||
{
|
||||
var element = bufferElements[firstElementIndex + n];
|
||||
uint elementNumberValue = 0;
|
||||
string elementStringValue = null;
|
||||
|
||||
var expectedPosition = element.BufferOffset - segmentStartPosition;
|
||||
Assert.AreEqual(expectedPosition, reader.Position);
|
||||
|
||||
switch (element.Type)
|
||||
{
|
||||
case ElementReference.BufferElementType.Byte:
|
||||
{
|
||||
elementNumberValue = reader.ReadByte();
|
||||
}
|
||||
break;
|
||||
case ElementReference.BufferElementType.TwoByteInt:
|
||||
{
|
||||
elementNumberValue = reader.ReadTwoByteInteger();
|
||||
}
|
||||
break;
|
||||
case ElementReference.BufferElementType.FourByteInt:
|
||||
{
|
||||
elementNumberValue = reader.ReadFourByteInteger();
|
||||
}
|
||||
break;
|
||||
case ElementReference.BufferElementType.VariableSizeInt:
|
||||
{
|
||||
elementNumberValue = reader.ReadVariableByteInteger();
|
||||
}
|
||||
break;
|
||||
case ElementReference.BufferElementType.String:
|
||||
{
|
||||
elementStringValue = reader.ReadString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (elementStringValue != null)
|
||||
{
|
||||
Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
|
||||
}
|
||||
}
|
||||
|
||||
// confirm end of stream
|
||||
Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
|
||||
Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Report_Correct_Length_For_Full_Buffer()
|
||||
{
|
||||
var buffer = new byte[] { 5, 6, 7, 8, 9 };
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(buffer, 0, 5);
|
||||
|
||||
Assert.IsFalse(reader.EndOfStream);
|
||||
Assert.AreEqual(5, reader.BytesLeft);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Report_Correct_Length_For_Partial_End_Buffer()
|
||||
{
|
||||
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
|
||||
// The used buffer contains more data than used!
|
||||
reader.SetBuffer(buffer, 5, 5);
|
||||
|
||||
Assert.IsFalse(reader.EndOfStream);
|
||||
Assert.AreEqual(5, reader.BytesLeft);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Report_Correct_Length_For_Partial_Start_Buffer()
|
||||
{
|
||||
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
|
||||
// The used buffer contains more data than used!
|
||||
reader.SetBuffer(buffer, 0, 5);
|
||||
|
||||
Assert.IsFalse(reader.EndOfStream);
|
||||
Assert.AreEqual(5, reader.BytesLeft);
|
||||
}
|
||||
|
||||
|
||||
// Helper class to build up a reference to elements of various types in a buffer
|
||||
class ElementReference
|
||||
{
|
||||
public enum BufferElementType
|
||||
{
|
||||
Byte,
|
||||
TwoByteInt,
|
||||
FourByteInt,
|
||||
VariableSizeInt,
|
||||
String
|
||||
}
|
||||
|
||||
public const int NumBufferElementTypes = 5;
|
||||
|
||||
public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
|
||||
{
|
||||
Type = type;
|
||||
Size = size;
|
||||
NumberValue = numberValue;
|
||||
StringValue = stringValue;
|
||||
BufferOffset = bufferOffset;
|
||||
}
|
||||
|
||||
public int BufferOffset { get; }
|
||||
public uint NumberValue { get; }
|
||||
public int Size { get; }
|
||||
public string StringValue { get; }
|
||||
public BufferElementType Type { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Tests.Extensions;
|
||||
using MQTTnet.Tests.Helpers;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Formatter
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Helpers
|
||||
{
|
||||
public static class MqttClientExtensions
|
||||
{
|
||||
public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
return new TestApplicationMessageReceivedHandler(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Extensions
|
||||
namespace MQTTnet.Tests.Helpers
|
||||
{
|
||||
public static class MqttPacketWriterExtensions
|
||||
{
|
||||
@@ -0,0 +1,28 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MQTTnet.Tests.Helpers
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
public static object GetFieldValue(this object source, string fieldName)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field == null)
|
||||
{
|
||||
throw new ArgumentException($"Field {fieldName} not found.");
|
||||
}
|
||||
|
||||
return field.GetValue(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
|
||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent.
|
||||
|
||||
await Task.Delay(1000);
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,17 @@ namespace MQTTnet.Tests.Mockups
|
||||
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_receivedEventArgs)
|
||||
{
|
||||
return _receivedEventArgs.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
|
||||
{
|
||||
get
|
||||
|
||||
@@ -238,6 +238,16 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
public MqttClientOptions CreateDefaultClientOptions()
|
||||
{
|
||||
return CreateDefaultClientOptionsBuilder().Build();
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
||||
{
|
||||
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
|
||||
}
|
||||
|
||||
public ILowLevelMqttClient CreateLowLevelClient()
|
||||
{
|
||||
lock (_clients)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttApplicationMessageValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_TopicAlias_And_MQTT_311()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_TopicAlias_And_MQTT_500()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_UserProperties_And_MQTT_500()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(
|
||||
new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(),
|
||||
MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(
|
||||
new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(),
|
||||
MqttProtocolVersion.V311);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClientOptionsValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_UserProperties_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Throw_When_Using_UserProperties_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttTopicValidator_Tests
|
||||
public sealed class MqttTopicValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Plus()
|
||||
public void Invalid_Topic_Empty()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Empty()
|
||||
public void Invalid_Topic_Plus()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,27 +8,48 @@ using MQTTnet.Formatter;
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class Protocol_Tests
|
||||
public sealed class Protocol_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Encode_Four_Byte_Integer()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4, 4);
|
||||
|
||||
|
||||
for (uint value = 0; value < 268435455; value++)
|
||||
{
|
||||
writer.WriteVariableByteInteger(value);
|
||||
|
||||
|
||||
var buffer = writer.GetBuffer();
|
||||
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(buffer, 0, writer.Length);
|
||||
var checkValue = reader.ReadVariableByteInteger();
|
||||
|
||||
Assert.AreEqual(value, checkValue);
|
||||
|
||||
|
||||
writer.Reset(0);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Encode_Two_Byte_Integer()
|
||||
{
|
||||
var writer = new MqttBufferWriter(2, 2);
|
||||
|
||||
for (ushort value = 0; value < ushort.MaxValue; value++)
|
||||
{
|
||||
writer.WriteTwoByteInteger(value);
|
||||
|
||||
var buffer = writer.GetBuffer();
|
||||
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(buffer, 0, writer.Length);
|
||||
var checkValue = reader.ReadTwoByteInteger();
|
||||
|
||||
Assert.AreEqual(value, checkValue);
|
||||
|
||||
writer.Reset(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,7 +636,7 @@ namespace MQTTnet.Tests.Server
|
||||
// nothing
|
||||
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
await c1.DisconnectAsync();
|
||||
await c1.TryDisconnectAsync();
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
@@ -60,5 +61,28 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Injected_Application_Message()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
MqttApplicationMessage interceptedMessage = null;
|
||||
server.InterceptingPublishAsync += eventArgs =>
|
||||
{
|
||||
interceptedMessage = eventArgs.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(interceptedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,104 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttSubscriptionsManager_SubscribeWildcard1()
|
||||
{
|
||||
await SubscribeToTopic("house/+/room");
|
||||
await SubscribeToTopic("house/+/room/+");
|
||||
|
||||
CheckIsSubscribed("house/1/room");
|
||||
CheckIsSubscribed("house/1/room/bed");
|
||||
CheckIsSubscribed("house/1/room/chair");
|
||||
CheckIsSubscribed("house/2/room/bed");
|
||||
CheckIsSubscribed("house/2/room/chair");
|
||||
|
||||
CheckIsNotSubscribed("house/1/room/bed/cover");
|
||||
CheckIsNotSubscribed("house/1/study/bed");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttSubscriptionsManager_SubscribeWildcard2()
|
||||
{
|
||||
await SubscribeToTopic("house/+/room");
|
||||
await SubscribeToTopic("house/+/room/#");
|
||||
|
||||
CheckIsSubscribed("house/1/room");
|
||||
CheckIsSubscribed("house/1/room/bed");
|
||||
CheckIsSubscribed("house/2/room");
|
||||
CheckIsSubscribed("house/2/room/bed");
|
||||
CheckIsSubscribed("house/2/room/bed/cover");
|
||||
|
||||
CheckIsNotSubscribed("house/1/level");
|
||||
CheckIsNotSubscribed("house/1/level/door");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttSubscriptionsManager_SubscribeWildcard3()
|
||||
{
|
||||
await SubscribeToTopic("house/1/room");
|
||||
await SubscribeToTopic("house/1/room/+");
|
||||
|
||||
CheckIsSubscribed("house/1/room");
|
||||
CheckIsSubscribed("house/1/room/bed");
|
||||
CheckIsSubscribed("house/1/room/chair");
|
||||
|
||||
CheckIsNotSubscribed("house/2/room");
|
||||
CheckIsNotSubscribed("house/2/room/bed/cover");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttSubscriptionsManager_SubscribeWildcard4()
|
||||
{
|
||||
await SubscribeToTopic("house/1/+/+");
|
||||
|
||||
CheckIsSubscribed("house/1/room/bed");
|
||||
CheckIsSubscribed("house/1/room/chair");
|
||||
|
||||
CheckIsNotSubscribed("house/1/room");
|
||||
CheckIsNotSubscribed("house/1/room/bed/cover");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttSubscriptionsManager_SubscribeWildcard5()
|
||||
{
|
||||
await SubscribeToTopic("house/1/+/#");
|
||||
|
||||
CheckIsSubscribed("house/1/room/bed");
|
||||
CheckIsSubscribed("house/1/room/chair");
|
||||
CheckIsSubscribed("house/1/room/chair/leg");
|
||||
CheckIsSubscribed("house/1/level/window");
|
||||
CheckIsSubscribed("house/1/level/door");
|
||||
|
||||
CheckIsNotSubscribed("house/2/room/bed");
|
||||
}
|
||||
|
||||
async Task SubscribeToTopic(string topic)
|
||||
{
|
||||
var sp = new MqttSubscribePacket
|
||||
{
|
||||
TopicFilters = new List<MqttTopicFilter>
|
||||
{
|
||||
new MqttTopicFilter { Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce },
|
||||
}
|
||||
};
|
||||
|
||||
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
|
||||
}
|
||||
|
||||
void CheckIsSubscribed(string topic)
|
||||
{
|
||||
var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
|
||||
Assert.IsTrue(result.IsSubscribed);
|
||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||
}
|
||||
|
||||
void CheckIsNotSubscribed(string topic)
|
||||
{
|
||||
var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
|
||||
Assert.IsFalse(result.IsSubscribed);
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace MQTTnet.Tests.Server
|
||||
var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
|
||||
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
dummyClient.Dispose();
|
||||
|
||||
await LongTestDelay();
|
||||
await LongTestDelay();
|
||||
|
||||
// Now inject messages which are appended to the queue of the client.
|
||||
await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
@@ -121,10 +121,9 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||
}
|
||||
}
|
||||
|
||||
#if !NET452
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(MqttProtocolVersion.V310)]
|
||||
[DataRow(MqttProtocolVersion.V311)]
|
||||
[DataRow(MqttProtocolVersion.V500)]
|
||||
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
|
||||
@@ -150,8 +149,6 @@ namespace MQTTnet.Tests.Server
|
||||
// Try to connect 50 clients at the same time.
|
||||
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||
|
||||
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
||||
|
||||
await LongTestDelay();
|
||||
@@ -162,13 +159,12 @@ namespace MQTTnet.Tests.Server
|
||||
var sendClient = await testEnvironment.ConnectClient(option2);
|
||||
await sendClient.PublishStringAsync("aaa", "1");
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(true, hasReceive);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||
|
||||
@@ -13,12 +13,56 @@ using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Helpers;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class Subscribe_Tests : BaseTestClass
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("A", "A", true)]
|
||||
[DataRow("A", "B", false)]
|
||||
[DataRow("A", "#", true)]
|
||||
[DataRow("A", "+", true)]
|
||||
[DataRow("A/B", "A/B", true)]
|
||||
[DataRow("A/B", "A/+", true)]
|
||||
[DataRow("A/B", "A/#", true)]
|
||||
[DataRow("A/B/C", "A/B/C", true)]
|
||||
[DataRow("A/B/C", "A/+/C", true)]
|
||||
[DataRow("A/B/C", "A/+/+", true)]
|
||||
[DataRow("A/B/C", "A/+/#", true)]
|
||||
[DataRow("A/B/C/D", "A/B/C/D", true)]
|
||||
[DataRow("A/B/C/D", "A/+/C/+", true)]
|
||||
[DataRow("A/B/C/D", "A/+/C/#", true)]
|
||||
[DataRow("A/B/C", "A/B/+", true)]
|
||||
[DataRow("A/B1/B2/C", "A/+/C", false)]
|
||||
public async Task Subscription_Roundtrip(string topic, string filter, bool shouldWork)
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
await receiver.SubscribeAsync(filter).ConfigureAwait(false);
|
||||
var receivedMessages = receiver.TrackReceivedMessages();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false);
|
||||
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
if (shouldWork)
|
||||
{
|
||||
Assert.AreEqual(1, receivedMessages.Count, message: "The filter should work!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, receivedMessages.Count, message: "The filter should not work!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Deny_Invalid_Topic()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
@@ -10,33 +9,51 @@ namespace MQTTnet.Tests.Server
|
||||
[TestClass]
|
||||
public sealed class Will_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Intercept_Will_Message()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
|
||||
MqttApplicationMessage willMessage = null;
|
||||
server.InterceptingPublishAsync += eventArgs =>
|
||||
{
|
||||
willMessage = eventArgs.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
Assert.IsNotNull(willMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
|
||||
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
||||
var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||
await sender.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
var c1 = await testEnvironment.ConnectClient();
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
c1.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||
await c2.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
Assert.AreEqual(0, receivedMessagesCount);
|
||||
Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,24 +64,17 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
c1.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
|
||||
await Task.Delay(1000);
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||
|
||||
long _bytesReceived;
|
||||
long _bytesSent;
|
||||
Statistics _statistics; // mutable struct, don't make readonly!
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
{
|
||||
@@ -51,9 +50,9 @@ namespace MQTTnet.Adapter
|
||||
_logger = logger.WithSource(nameof(MqttChannelAdapter));
|
||||
}
|
||||
|
||||
public long BytesReceived => Interlocked.Read(ref _bytesReceived);
|
||||
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
|
||||
|
||||
public long BytesSent => Interlocked.Read(ref _bytesSent);
|
||||
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
|
||||
|
||||
public X509Certificate2 ClientCertificate => _channel.ClientCertificate;
|
||||
|
||||
@@ -147,7 +146,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
_packetInspector?.EndReceivePacket();
|
||||
|
||||
Interlocked.Add(ref _bytesSent, receivedPacket.TotalLength);
|
||||
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
||||
|
||||
if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
@@ -181,11 +180,7 @@ namespace MQTTnet.Adapter
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ResetStatistics()
|
||||
{
|
||||
Interlocked.Exchange(ref _bytesReceived, 0L);
|
||||
Interlocked.Exchange(ref _bytesSent, 0L);
|
||||
}
|
||||
public void ResetStatistics() => _statistics.Reset();
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -216,7 +211,7 @@ namespace MQTTnet.Adapter
|
||||
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
|
||||
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -459,5 +454,17 @@ namespace MQTTnet.Adapter
|
||||
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
|
||||
private struct Statistics
|
||||
{
|
||||
public long _bytesReceived;
|
||||
public long _bytesSent;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Volatile.Write(ref _bytesReceived, 0);
|
||||
Volatile.Write(ref _bytesSent, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,12 +82,7 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
try
|
||||
{
|
||||
var eventArgs = new InspectMqttPacketEventArgs
|
||||
{
|
||||
Buffer = buffer,
|
||||
Direction = direction
|
||||
};
|
||||
|
||||
var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
|
||||
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
}
|
||||
|
||||
@@ -2,20 +2,35 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason code.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
|
||||
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,62 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptionsBuilder
|
||||
{
|
||||
MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
string _reasonString;
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
uint _sessionExpiryInterval;
|
||||
List<MqttUserProperty> _userProperties;
|
||||
|
||||
public MqttClientDisconnectOptions Build()
|
||||
{
|
||||
return new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = _reason,
|
||||
ReasonString = _reasonString
|
||||
ReasonString = _reasonString,
|
||||
UserProperties = _userProperties,
|
||||
SessionExpiryInterval = _sessionExpiryInterval
|
||||
};
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
|
||||
{
|
||||
_sessionExpiryInterval = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> userProperties)
|
||||
{
|
||||
_userProperties = userProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_userProperties == null)
|
||||
{
|
||||
_userProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_userProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum only contains values which are valid when a client sends the reason to the server.
|
||||
/// </summary>
|
||||
public enum MqttClientDisconnectOptionsReason
|
||||
{
|
||||
NormalDisconnection = 0,
|
||||
DisconnectWithWillMessage = 4,
|
||||
UnspecifiedError = 128,
|
||||
MalformedPacket = 129,
|
||||
ProtocolError = 130,
|
||||
ImplementationSpecificError = 131,
|
||||
TopicNameInvalid = 144,
|
||||
ReceiveMaximumExceeded = 147,
|
||||
TopicAliasInvalid = 148,
|
||||
PacketTooLarge = 149,
|
||||
MessageRateTooHigh = 150,
|
||||
QuotaExceeded = 151,
|
||||
AdministrativeAction = 152,
|
||||
PayloadFormatInvalid = 153
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientDisconnectOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.ReasonString?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.ReasonString));
|
||||
}
|
||||
|
||||
if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection)
|
||||
{
|
||||
Throw(nameof(options.Reason));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,47 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public bool ClientWasConnected { get; internal set; }
|
||||
public MqttClientDisconnectedEventArgs(
|
||||
bool clientWasConnected,
|
||||
MqttClientConnectResult connectResult,
|
||||
MqttClientDisconnectReason reason,
|
||||
string reasonString,
|
||||
List<MqttUserProperty> userProperties,
|
||||
Exception exception)
|
||||
{
|
||||
ClientWasConnected = clientWasConnected;
|
||||
ConnectResult = connectResult;
|
||||
Exception = exception;
|
||||
Reason = reason;
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public Exception Exception { get; internal set; }
|
||||
public bool ClientWasConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets the authentication result.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; internal set; }
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; internal set; }
|
||||
|
||||
public string ReasonString { get; internal set; }
|
||||
public MqttClientDisconnectReason Reason { get; }
|
||||
|
||||
public string ReasonString { get; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
-102
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
@@ -19,7 +20,6 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClient : Disposable, IMqttClient
|
||||
{
|
||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
||||
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||
|
||||
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
||||
@@ -32,19 +32,25 @@ namespace MQTTnet.Client
|
||||
readonly object _disconnectLock = new object();
|
||||
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
||||
|
||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
readonly IMqttNetLogger _rootLogger;
|
||||
|
||||
IMqttChannelAdapter _adapter;
|
||||
CancellationTokenSource _backgroundCancellationTokenSource;
|
||||
|
||||
bool _cleanDisconnectInitiated;
|
||||
volatile int _connectionStatus;
|
||||
MqttClientDisconnectReason _disconnectReason;
|
||||
|
||||
// The value for this field can be set from two different enums.
|
||||
// They contain the same values but the set is reduced in one case.
|
||||
int _disconnectReason;
|
||||
string _disconnectReasonString;
|
||||
List<MqttUserProperty> _disconnectUserProperties;
|
||||
|
||||
Task _keepAlivePacketsSenderTask;
|
||||
DateTime _lastPacketSentTimestamp;
|
||||
|
||||
CancellationTokenSource _mqttClientAlive;
|
||||
MqttPacketDispatcher _packetDispatcher;
|
||||
Task _packetReceiverTask;
|
||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||
@@ -86,25 +92,15 @@ namespace MQTTnet.Client
|
||||
add => _inspectPacketEvent.AddHandler(value);
|
||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
|
||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
|
||||
public MqttClientOptions Options { get; private set; }
|
||||
|
||||
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
ThrowIfOptionsInvalid(options);
|
||||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
|
||||
@@ -123,28 +119,29 @@ namespace MQTTnet.Client
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher = new MqttPacketDispatcher();
|
||||
|
||||
_backgroundCancellationTokenSource = new CancellationTokenSource();
|
||||
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
|
||||
_mqttClientAlive = new CancellationTokenSource();
|
||||
var mqttClientAliveToken = _mqttClientAlive.Token;
|
||||
|
||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
|
||||
_adapter = adapter;
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions);
|
||||
await adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
connectResult = await AuthenticateAsync(options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to the general timeout specified in the options if the user passed
|
||||
// CancellationToken.None or similar.
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
@@ -158,7 +155,7 @@ namespace MQTTnet.Client
|
||||
|
||||
if (keepAliveInterval != TimeSpan.Zero)
|
||||
{
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(mqttClientAliveToken), mqttClientAliveToken);
|
||||
}
|
||||
|
||||
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
||||
@@ -175,7 +172,7 @@ namespace MQTTnet.Client
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
|
||||
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
||||
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
|
||||
@@ -195,6 +192,7 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
|
||||
var clientWasConnected = IsConnected;
|
||||
|
||||
if (DisconnectIsPendingOrFinished())
|
||||
{
|
||||
return;
|
||||
@@ -202,14 +200,34 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
if (!clientWasConnected)
|
||||
{
|
||||
ThrowNotConnected();
|
||||
}
|
||||
|
||||
_disconnectReason = (int)options.Reason;
|
||||
_cleanDisconnectInitiated = true;
|
||||
|
||||
if (clientWasConnected)
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
||||
// must be throw to let the caller know that the disconnect is not a clean one.
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
var disconnectPacket = _packetFactories.Disconnect.Create(options);
|
||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -241,7 +259,12 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var publishPacket = _packetFactories.Publish.Create(applicationMessage);
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||
|
||||
switch (applicationMessage.QualityOfServiceLevel)
|
||||
{
|
||||
@@ -298,13 +321,29 @@ namespace MQTTnet.Client
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
|
||||
}
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var subscribePacket = _packetFactories.Subscribe.Create(options);
|
||||
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttSubAckPacket subAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
|
||||
}
|
||||
@@ -316,13 +355,34 @@ namespace MQTTnet.Client
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
foreach (var topicFilter in options.TopicFilters)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var unsubscribePacket = _packetFactories.Unsubscribe.Create(options);
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttUnsubAckPacket unsubAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
|
||||
}
|
||||
@@ -347,7 +407,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (!eventArgs.ProcessingFailed)
|
||||
{
|
||||
var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
|
||||
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
||||
return SendAsync(pubAckPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -355,7 +415,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (!eventArgs.ProcessingFailed)
|
||||
{
|
||||
var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
|
||||
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
||||
return SendAsync(pubRecPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -373,12 +433,12 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
var connectPacket = _packetFactories.Connect.Create(options);
|
||||
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||
|
||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var clientConnectResultFactory = new MqttClientConnectResultFactory();
|
||||
result = clientConnectResultFactory.Create(connAckPacket, options.ProtocolVersion);
|
||||
result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -399,19 +459,19 @@ namespace MQTTnet.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Dispose();
|
||||
_backgroundCancellationTokenSource = null;
|
||||
_mqttClientAlive?.Dispose();
|
||||
_mqttClientAlive = null;
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
_adapter = null;
|
||||
|
||||
|
||||
_packetDispatcher?.Dispose();
|
||||
_packetDispatcher = null;
|
||||
}
|
||||
@@ -422,6 +482,26 @@ namespace MQTTnet.Client
|
||||
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
|
||||
}
|
||||
|
||||
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
|
||||
{
|
||||
var backgroundCancellationToken = _mqttClientAlive.Token;
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'.", Options.ChannelOptions);
|
||||
await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||
{
|
||||
TryInitiateDisconnect();
|
||||
@@ -448,10 +528,10 @@ namespace MQTTnet.Client
|
||||
try
|
||||
{
|
||||
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
||||
|
||||
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
|
||||
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
|
||||
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
|
||||
|
||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
|
||||
|
||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
||||
}
|
||||
@@ -467,14 +547,13 @@ namespace MQTTnet.Client
|
||||
|
||||
_logger.Info("Disconnected.");
|
||||
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs
|
||||
{
|
||||
ClientWasConnected = clientWasConnected,
|
||||
Exception = exception,
|
||||
ConnectResult = connectResult,
|
||||
Reason = _disconnectReason,
|
||||
ReasonString = _disconnectReasonString
|
||||
};
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs(
|
||||
clientWasConnected,
|
||||
connectResult,
|
||||
(MqttClientDisconnectReason)_disconnectReason,
|
||||
_disconnectReasonString,
|
||||
_disconnectUserProperties,
|
||||
exception);
|
||||
|
||||
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
||||
// when trying to reconnect in that handler etc.
|
||||
@@ -535,9 +614,8 @@ namespace MQTTnet.Client
|
||||
|
||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
||||
{
|
||||
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||
|
||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
|
||||
return eventArgs;
|
||||
@@ -556,8 +634,9 @@ namespace MQTTnet.Client
|
||||
|
||||
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
_disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode;
|
||||
_disconnectReason = (int)disconnectPacket.ReasonCode;
|
||||
_disconnectReasonString = disconnectPacket.ReasonString;
|
||||
_disconnectUserProperties = disconnectPacket.UserProperties;
|
||||
|
||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
||||
@@ -601,7 +680,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
// The packet is unknown. Probably due to a restart of the client.
|
||||
// So wen send this to the server to trigger a full resend of the message.
|
||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||
return SendAsync(pubRelPacket, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -610,7 +689,7 @@ namespace MQTTnet.Client
|
||||
|
||||
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
return SendAsync(pubCompPacket, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -619,7 +698,6 @@ namespace MQTTnet.Client
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(pubAckPacket);
|
||||
}
|
||||
|
||||
@@ -637,7 +715,7 @@ namespace MQTTnet.Client
|
||||
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
|
||||
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -695,7 +773,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
throw new MqttProtocolViolationException(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,17 +781,40 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new MqttCommunicationException("The client is not connected.");
|
||||
ThrowNotConnected();
|
||||
}
|
||||
}
|
||||
|
||||
static void ThrowIfOptionsInvalid(MqttClientOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
if (options.ValidateFeatures)
|
||||
{
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(options);
|
||||
}
|
||||
}
|
||||
|
||||
static void ThrowNotConnected()
|
||||
{
|
||||
throw new MqttCommunicationException("The client is not connected.");
|
||||
}
|
||||
|
||||
void TryInitiateDisconnect()
|
||||
{
|
||||
lock (_disconnectLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -833,7 +934,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
exception = aggregateException.GetBaseException();
|
||||
}
|
||||
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
@@ -912,36 +1013,5 @@ namespace MQTTnet.Client
|
||||
_logger.Verbose("Stopped sending keep alive packets.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task WaitForTaskAsync(Task task, Task sender)
|
||||
{
|
||||
if (task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task == sender)
|
||||
{
|
||||
// Return here to avoid deadlocks, but first any eventual exception in the task
|
||||
// must be handled to avoid not getting an unhandled task exception
|
||||
if (!task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// By accessing the Exception property the exception is considered handled and will
|
||||
// not result in an unhandled task exception later by the finalizer
|
||||
_logger.Warning(task.Exception, "Error while waiting for background task.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientExtensions
|
||||
{
|
||||
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
||||
public static Task DisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null,
|
||||
uint sessionExpiryInterval = 0,
|
||||
List<MqttUserProperty> userProperties = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
return client.DisconnectAsync(
|
||||
new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString
|
||||
},
|
||||
CancellationToken.None);
|
||||
var disconnectOptions = new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString,
|
||||
SessionExpiryInterval = sessionExpiryInterval,
|
||||
UserProperties = userProperties
|
||||
};
|
||||
|
||||
return client.DisconnectAsync(disconnectOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishBinaryAsync(
|
||||
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
|
||||
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<bool> TryDisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore all errors.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
|
||||
@@ -10,20 +10,12 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientCertificateValidationEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientCertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, IMqttClientChannelOptions clientOptions)
|
||||
{
|
||||
Certificate = certificate ?? throw new ArgumentNullException(nameof(certificate));
|
||||
Chain = chain ?? throw new ArgumentNullException(nameof(chain));
|
||||
SslPolicyErrors = sslPolicyErrors;
|
||||
ClientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
|
||||
}
|
||||
public X509Certificate Certificate { get; set; }
|
||||
|
||||
public X509Certificate Certificate { get; }
|
||||
public X509Chain Chain { get; set; }
|
||||
|
||||
public X509Chain Chain { get; }
|
||||
public SslPolicyErrors SslPolicyErrors { get; set; }
|
||||
|
||||
public IMqttClientChannelOptions ClientOptions { get; }
|
||||
|
||||
public SslPolicyErrors SslPolicyErrors { get; }
|
||||
public IMqttClientChannelOptions ClientOptions { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,11 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs)
|
||||
{
|
||||
var sslPolicyErrors = eventArgs.SslPolicyErrors;
|
||||
|
||||
if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
|
||||
{
|
||||
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
if (eventArgs.SslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (eventArgs.Chain.ChainStatus.Any(c =>
|
||||
c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
|
||||
{
|
||||
|
||||
@@ -14,13 +14,13 @@ namespace MQTTnet.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod { get; set; }
|
||||
|
||||
@@ -58,6 +58,10 @@ namespace MQTTnet.Client
|
||||
/// </summary>
|
||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum packet size.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint MaximumPacketSize { get; set; }
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
@@ -65,24 +69,26 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the receive messages.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request problem information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request response information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// The time after a session expires when it's not actively used.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
@@ -95,6 +101,7 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
/// This gives the maximum length of the topic alias.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum { get; set; }
|
||||
|
||||
@@ -103,8 +110,10 @@ namespace MQTTnet.Client
|
||||
/// client.
|
||||
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
|
||||
/// correctly.
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
|
||||
/// properly.
|
||||
/// <remarks>
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not
|
||||
/// connect properly.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public bool TryPrivate { get; set; } = true;
|
||||
|
||||
@@ -115,28 +124,41 @@ namespace MQTTnet.Client
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this feature is enabled the client will check if used properties are supported in the selected protocol
|
||||
/// version.
|
||||
/// This feature can be validated if an application message is generated one time but sent via different protocol
|
||||
/// versions.
|
||||
/// Default values are applied if the validation is off and features are not supported.
|
||||
/// </summary>
|
||||
public bool ValidateFeatures { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content type of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the correlation data of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] WillCorrelationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the will delay interval.
|
||||
/// This is the time between the client disconnect and the time the will message will be sent.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillDelayInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message expiry interval of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillMessageExpiryInterval { get; set; }
|
||||
|
||||
@@ -147,8 +169,9 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload format indicator of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; }
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the QoS level of the will message.
|
||||
@@ -157,6 +180,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response topic of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillResponseTopic { get; set; }
|
||||
|
||||
@@ -172,6 +196,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> WillUserProperties { get; set; }
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace MQTTnet.Client
|
||||
Certificates = _tlsParameters.Certificates?.ToList(),
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
||||
#endif
|
||||
};
|
||||
@@ -74,6 +74,8 @@ namespace MQTTnet.Client
|
||||
|
||||
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
||||
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
|
||||
|
||||
return _options;
|
||||
}
|
||||
|
||||
@@ -337,16 +339,6 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
@@ -14,9 +13,9 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } = MqttClientDefaultCertificateValidationHandler.Handle;
|
||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
#if NET48 || NETCOREAPP3_1_OR_GREATER
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
@@ -28,14 +27,14 @@ namespace MQTTnet.Client
|
||||
public IEnumerable<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get;set; }
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
#endif
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
public bool IgnoreCertificateRevocationErrors { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.WillContentType?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillContentType));
|
||||
}
|
||||
|
||||
if (options.UserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.UserProperties));
|
||||
}
|
||||
|
||||
if (options.RequestProblemInformation)
|
||||
{
|
||||
// Since this value is a boolean and true by default, validation would
|
||||
// require a nullable boolean.
|
||||
//Throw(nameof(options.RequestProblemInformation));
|
||||
}
|
||||
|
||||
if (options.RequestResponseInformation)
|
||||
{
|
||||
Throw(nameof(options.RequestResponseInformation));
|
||||
}
|
||||
|
||||
if (options.ReceiveMaximum > 0)
|
||||
{
|
||||
Throw(nameof(options.ReceiveMaximum));
|
||||
}
|
||||
|
||||
if (options.MaximumPacketSize > 0)
|
||||
{
|
||||
Throw(nameof(options.MaximumPacketSize));
|
||||
}
|
||||
|
||||
// Authentication relevant properties.
|
||||
|
||||
if (options.AuthenticationData?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.AuthenticationData));
|
||||
}
|
||||
|
||||
if (options.AuthenticationMethod?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.AuthenticationMethod));
|
||||
}
|
||||
|
||||
// Will relevant properties.
|
||||
|
||||
if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
|
||||
{
|
||||
Throw(nameof(options.WillPayloadFormatIndicator));
|
||||
}
|
||||
|
||||
if (options.WillContentType?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillContentType));
|
||||
}
|
||||
|
||||
if (options.WillCorrelationData?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillCorrelationData));
|
||||
}
|
||||
|
||||
if (options.WillResponseTopic?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillResponseTopic));
|
||||
}
|
||||
|
||||
if (options.WillDelayInterval > 0)
|
||||
{
|
||||
Throw(nameof(options.WillDelayInterval));
|
||||
}
|
||||
|
||||
if (options.WillMessageExpiryInterval > 0)
|
||||
{
|
||||
Throw(nameof(options.WillMessageExpiryInterval));
|
||||
}
|
||||
|
||||
if (options.WillUserProperties?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.WillUserProperties));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user