Compare commits

..
232 changed files with 3293 additions and 7827 deletions
+1 -11
View File
@@ -1,11 +1 @@
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar).
* [Client] Added hot reload of client certificates (#1781).
* [Client] Added several new option builders and aligned usage (#1781, BREAKING CHANGE!).
* [Client] Added support for _RemoteCertificateValidationCallback_ for .NET 4.5.2, 4.6.1 and 4.8 (#1806, thanks to @troky).
* [Client] Fixed wrong logging of obsolete feature when connection was not successful (#1801, thanks to @ramonsmits).
* [Client] Fixed _NullReferenceException_ when performing several actions when not connected (#1800, thanks to @ramonsmits).
* [RpcClient] Added support for passing custom parameters to topic generation context (#1798, thanks to @Temppus).
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
* [Server] Expired sessions will no longer be used when a client connects (#1756).
* [Server] Fixed an issue in connection handling for ASP.NET connections (#1819, thanks to @CZEMacLeod).
+6 -3
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.3.0.${{github.run_number}}" VERSION: "4.1.3.${{github.run_number}}"
jobs: jobs:
build: build:
@@ -22,7 +22,6 @@ jobs:
dotnet-version: | dotnet-version: |
3.1.x 3.1.x
6.0.x 6.0.x
7.0.x
- name: Setup MSBuild - name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1 uses: microsoft/setup-msbuild@v1.1
@@ -61,4 +60,8 @@ jobs:
- name: Publish MyGet nugets - name: Publish MyGet nugets
if: ${{ github.event_name == 'push' }} 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 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
+1 -7
View File
@@ -240,14 +240,8 @@ 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_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_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/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/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@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/=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/=Tnet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@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/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -17
View File
@@ -4,7 +4,7 @@
</p> </p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml) [![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet) [![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg) ![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg)
[![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -50,22 +50,6 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients * Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Getting Started
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
Use these command in the Package Manager console to install MQTTnet manually:
```
Install-Package MQTTnet
```
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+23 -69
View File
@@ -16,28 +16,6 @@ namespace MQTTnet.Samples.Client;
public static class Client_Connection_Samples 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() public static async Task Connect_Client()
{ {
/* /*
@@ -135,15 +113,15 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io") var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
.WithTlsOptions( .WithTls(
o => o =>
{ {
// The used public broker sometimes has invalid certificates. This sample accepts all // The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments. // certificates. This should not be used in live environments.
o.WithCertificateValidationHandler(_ => true); o.CertificateValidationHandler = _ => true;
// The default value is determined by the OS. Set manually to force version. // The default value is determined by the OS. Set manually to force version.
o.WithSslProtocols(SslProtocols.Tls12); o.SslProtocol = SslProtocols.Tls12;
}) })
.Build(); .Build();
@@ -160,7 +138,7 @@ public static class Client_Connection_Samples
{ {
/* /*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection. * 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 implementation from WebSocket4Net is used. It provides more * Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms. * encryption algorithms and supports more platforms.
* *
* This is a modified version of the sample _Connect_Client_! See other sample for more details. * This is a modified version of the sample _Connect_Client_! See other sample for more details.
@@ -170,7 +148,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -192,7 +170,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -215,11 +193,13 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions( .WithTls(
o => o.WithCertificateValidationHandler( o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all // The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments. // certificates. This should not be used in live environments.
_ => true)) o.CertificateValidationHandler = _ => true;
})
.Build(); .Build();
// In MQTTv5 the response contains much more information. // In MQTTv5 the response contains much more information.
@@ -234,31 +214,6 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_With_Amazon_AWS()
{
/*
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
*
* The broker requires special settings which are set here.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important!
.WithoutPacketFragmentation()
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("The MQTT client is connected.");
await mqttClient.DisconnectAsync();
}
}
public static async Task Disconnect_Clean() public static async Task Disconnect_Clean()
{ {
/* /*
@@ -277,7 +232,7 @@ public static class Client_Connection_Samples
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection. // Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0! // Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError); await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
} }
} }
@@ -314,19 +269,18 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883) var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTlsOptions( .WithTls(
o => o =>
{ {
o.WithCertificateValidationHandler( o.CertificateValidationHandler = eventArgs =>
eventArgs => {
{ eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.Subject.DumpToConsole(); eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole(); eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole(); eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole(); eventArgs.SslPolicyErrors.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole(); return true;
return true; };
});
}) })
.Build(); .Build();
@@ -343,7 +297,7 @@ public static class Client_Connection_Samples
/* /*
* This sample sends a PINGREQ packet to the server and waits for a reply. * This sample sends a PINGREQ packet to the server and waits for a reply.
* *
* This is only supported in MQTTv5.0.0+. * This is only supported in METTv5.0.0+.
*/ */
var mqttFactory = new MqttFactory(); var mqttFactory = new MqttFactory();
@@ -5,7 +5,6 @@
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -177,60 +176,4 @@ public static class Client_Subscribe_Samples
response.DumpToConsole(); response.DumpToConsole();
} }
} }
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
*/
mqttClient.ApplicationMessageReceivedAsync += async ea =>
{
ea.AutoAcknowledge = false;
async Task ProcessAsync()
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
await ea.AcknowledgeAsync(shutdownToken);
// WARNING: If process failures are not transient the message will be retried on every restart of the client
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
// the broker know processing failed
//
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
}
_ = Task.Run(ProcessAsync, shutdownToken);
};
}
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing, with:
* - a maximum concurrency limit based on Environment.ProcessorCount
*/
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
mqttClient.ApplicationMessageReceivedAsync += async ea =>
{
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
async Task ProcessAsync()
{
try
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
}
finally
{
concurrent.Release();
}
}
_ = Task.Run(ProcessAsync, shutdownToken);
};
}
} }
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
mqttClient.InspectPacketAsync += OnInspectPacket; mqttClient.InspectPackage += OnInspectPackage;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
} }
} }
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
{ {
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound) if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
{ {
+3 -1
View File
@@ -9,10 +9,12 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" /> <ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
+1 -18
View File
@@ -4,13 +4,7 @@
using System.Reflection; using System.Reflection;
Console.BackgroundColor = ConsoleColor.White; Console.WriteLine("Welcome to MQTTnet samples!");
Console.BackgroundColor = ConsoleColor.Red;
Console.Title = "Samples - MQTTnet";
Console.WriteLine("----------------------------------------------");
Console.WriteLine("- Welcome to MQTTnet samples! -");
Console.WriteLine("----------------------------------------------");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList(); var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
@@ -22,11 +16,7 @@ foreach (var sampleClass in sampleClasses)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample class (press Enter to continue): "); Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine(); var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0"); var selectedIndex = int.Parse(input ?? "0");
var selectedSampleClass = sampleClasses[selectedIndex]; var selectedSampleClass = sampleClasses[selectedIndex];
@@ -39,19 +29,12 @@ foreach (var sampleMethod in sampleMethods)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample (press Enter to continue): "); Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor();
input = Console.ReadLine(); input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0"); selectedIndex = int.Parse(input ?? "0");
var selectedSampleMethod = sampleMethods[selectedIndex]; var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Executing sample..."); Console.WriteLine("Executing sample...");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
try try
@@ -17,7 +17,7 @@ using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
public static class Server_ASP_NET_Samples public static class Server_Asp_Net_Samples
{ {
public static Task Start_Server_With_WebSockets_Support() public static Task Start_Server_With_WebSockets_Support()
{ {
@@ -1,53 +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 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();
}
}
}
@@ -7,8 +7,6 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Text.Json; using System.Text.Json;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
@@ -34,10 +32,7 @@ public static class Server_Retained_Messages_Samples
{ {
try try
{ {
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>(); eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded."); Console.WriteLine("Retained messages loaded.");
} }
catch (FileNotFoundException) catch (FileNotFoundException)
@@ -61,9 +56,7 @@ public static class Server_Retained_Messages_Samples
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list // used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event. // of retained messages must be provided in the loaded event.
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create); var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer); await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved."); Console.WriteLine("Retained messages saved.");
} }
@@ -86,59 +79,4 @@ public static class Server_Retained_Messages_Samples
Console.ReadLine(); Console.ReadLine();
} }
} }
sealed class MqttRetainedMessageModel
{
public string? ContentType { get; set; }
public byte[]? CorrelationData { get; set; }
public byte[]? Payload { get; set; }
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public string? ResponseTopic { get; set; }
public string? Topic { get; set; }
public List<MqttUserProperty>? UserProperties { get; set; }
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return new MqttRetainedMessageModel
{
Topic = message.Topic,
// Create a copy of the buffer from the payload segment because
// it cannot be serialized and deserialized with the JSON serializer.
Payload = message.PayloadSegment.ToArray(),
UserProperties = message.UserProperties,
ResponseTopic = message.ResponseTopic,
CorrelationData = message.CorrelationData,
ContentType = message.ContentType,
PayloadFormatIndicator = message.PayloadFormatIndicator,
QualityOfServiceLevel = message.QualityOfServiceLevel
// Other properties like "Retain" are not if interest in the storage.
// That's why a custom model makes sense.
};
}
public MqttApplicationMessage ToApplicationMessage()
{
return new MqttApplicationMessage
{
Topic = Topic,
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
PayloadFormatIndicator = PayloadFormatIndicator,
ResponseTopic = ResponseTopic,
CorrelationData = CorrelationData,
ContentType = ContentType,
UserProperties = UserProperties,
QualityOfServiceLevel = QualityOfServiceLevel,
Dup = false,
Retain = true
};
}
}
} }
-75
View File
@@ -1,75 +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.
// 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,6 +12,8 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" 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>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
@@ -18,7 +18,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Helpers; using MQTTnet.Tests.Extensions;
namespace MQTTnet.AspNetCore.Tests namespace MQTTnet.AspNetCore.Tests
{ {
@@ -35,7 +35,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe; connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection); var ctx = new MqttConnectionContext(serializer, connection);
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>())); await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None)); await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe; connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection); var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false); await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync(); var readResult = await pipe.Send.Reader.ReadAsync();
Assert.IsTrue(readResult.Buffer.Length > 20000); Assert.IsTrue(readResult.Buffer.Length > 20000);
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
{ {
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join(); var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count); var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
@@ -8,15 +8,17 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" /> <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" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="wwwroot\" /> <Folder Include="wwwroot\"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool); _sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool); _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
} }
#if NETCOREAPP3_1_OR_GREATER #if NETCOREAPP3_1 || NET5_0_OR_GREATER
public override ValueTask DisposeAsync() public override ValueTask DisposeAsync()
#else #else
public Task DisposeAsync() public Task DisposeAsync()
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose(); _socket?.Dispose();
#if NETCOREAPP3_1_OR_GREATER #if NETCOREAPP3_1 || NET5_0_OR_GREATER
return base.DisposeAsync(); return base.DisposeAsync();
} }
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#if NETCOREAPP3_1_OR_GREATER #if NETCOREAPP3_1 || NET5_0_OR_GREATER
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName> <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace> <RootNamespace>MQTTnet.AspNetCore</RootNamespace>
@@ -32,8 +32,7 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -51,11 +50,11 @@
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants> <DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' "> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' "> <ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup> </ItemGroup>
@@ -2,62 +2,38 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.IO.Pipelines;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp; using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using System;
using System.IO.Pipelines;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public sealed class MqttConnectionContext : IMqttChannelAdapter public sealed class MqttConnectionContext : IMqttChannelAdapter
{ {
readonly ConnectionContext _connection;
readonly AsyncLock _writerLock = new AsyncLock(); readonly AsyncLock _writerLock = new AsyncLock();
PipeReader _input; PipeReader _input;
PipeWriter _output; PipeWriter _output;
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection) public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{ {
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
_connection = connection ?? throw new ArgumentNullException(nameof(connection)); Connection = connection ?? throw new ArgumentNullException(nameof(connection));
if (!(_connection is TcpConnection tcp) || tcp.IsConnected) if (Connection.Transport != null)
{ {
_input = connection.Transport.Input; _input = Connection.Transport.Input;
_output = connection.Transport.Output; _output = Connection.Transport.Output;
}
}
public long BytesReceived { get; private set; }
public long BytesSent { get; private set; }
public X509Certificate2 ClientCertificate
{
get
{
// 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;
} }
} }
@@ -65,59 +41,47 @@ namespace MQTTnet.AspNetCore
{ {
get get
{ {
// mqtt over tcp #if NETCOREAPP3_1
#if NETCOREAPP3_1_OR_GREATER if (Connection?.RemoteEndPoint != null)
if (_connection.RemoteEndPoint != null)
{ {
return _connection.RemoteEndPoint.ToString(); return Connection.RemoteEndPoint.ToString();
} }
#endif #endif
// mqtt over websocket var connection = Http?.HttpContext?.Connection;
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>(); if (connection == null)
if (httpFeature?.RemoteIpAddress != null)
{ {
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString(); return Connection.ConnectionId;
} }
return null; return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
} }
} }
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; } public bool IsReadingPacket { get; private set; }
public bool IsSecureConnection IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
{
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) 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); await tcp.StartAsync().ConfigureAwait(false);
} }
_input = _connection.Transport.Input; _input = Connection.Transport.Input;
_output = _connection.Transport.Output; _output = Connection.Transport.Output;
} }
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -128,19 +92,16 @@ namespace MQTTnet.AspNetCore
return Task.CompletedTask; return Task.CompletedTask;
} }
public void Dispose()
{
_writerLock.Dispose();
}
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken) public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{ {
var input = Connection.Transport.Input;
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
ReadResult readResult; ReadResult readResult;
var readTask = _input.ReadAsync(cancellationToken); var readTask = input.ReadAsync(cancellationToken);
if (readTask.IsCompleted) if (readTask.IsCompleted)
{ {
readResult = readTask.Result; readResult = readTask.Result;
@@ -180,15 +141,15 @@ namespace MQTTnet.AspNetCore
// The buffer was sliced up to where it was consumed, so we can just advance to the start. // 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 // 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. // before yielding the read again.
_input.AdvanceTo(consumed, observed); input.AdvanceTo(consumed, observed);
} }
} }
} }
catch (Exception exception) catch (Exception e)
{ {
// completing the channel makes sure that there is no more data read after a protocol error // completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(exception); _input?.Complete(e);
_output?.Complete(exception); _output?.Complete(e);
throw; throw;
} }
finally finally
@@ -208,44 +169,25 @@ namespace MQTTnet.AspNetCore
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{ {
var formatter = PacketFormatterAdapter;
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false)) using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{ {
try 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)
{ {
var buffer = PacketFormatterAdapter.Encode(packet); BytesSent += msg.Length;
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();
} }
PacketFormatterAdapter.Cleanup();
} }
} }
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) public void Dispose()
{ {
// 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,34 +2,38 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Server; using MQTTnet.Server;
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{ {
IMqttNetLogger _logger = MqttNetNullLogger.Instance; IMqttNetLogger _logger = new MqttNetNullLogger();
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; } public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public void Dispose() public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
{ {
if (webSocket == null) if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
{
throw new ArgumentNullException(nameof(webSocket));
}
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
@@ -44,7 +48,7 @@ namespace MQTTnet.AspNetCore
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate); var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger)) using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
{ {
await clientHandler(channelAdapter).ConfigureAwait(false); await clientHandler(channelAdapter).ConfigureAwait(false);
} }
@@ -56,15 +60,8 @@ namespace MQTTnet.AspNetCore
} }
} }
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public void Dispose()
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
} }
} }
+23 -50
View File
@@ -2,23 +2,21 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public static class ReaderExtensions public static class ReaderExtensions
{ {
public static bool TryDecode(this MqttPacketFormatterAdapter formatter, public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input, in ReadOnlySequence<byte> input,
out MqttPacket packet, out MqttPacket packet,
out SequencePosition consumed, out SequencePosition consumed,
out SequencePosition observed, out SequencePosition observed,
out int bytesRead) out int bytesRead)
{ {
@@ -35,22 +33,22 @@ namespace MQTTnet.AspNetCore
return false; return false;
} }
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength)) var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{ {
return false; return false;
} }
var fixedHeader = copy.First.Span[0];
copy = copy.Slice(headerLength);
if (copy.Length < bodyLength) if (copy.Length < bodyLength)
{ {
return false; return false;
} }
var bodySlice = copy.Slice(0, bodyLength); var bodySlice = copy.Slice(0, bodyLength);
var bodySegment = GetArraySegment(ref bodySlice); var buffer = bodySlice.GetMemory().ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength); var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown) if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{ {
formatter.DetectProtocolVersion(receivedMqttPacket); formatter.DetectProtocolVersion(receivedMqttPacket);
@@ -63,41 +61,18 @@ namespace MQTTnet.AspNetCore
return true; return true;
} }
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input) static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
{ {
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment)) if (input.IsSingleSegment)
{ {
return segment; return input.First;
} }
// Should be rare // Should be rare
var array = input.ToArray(); return input.ToArray();
return new ArraySegment<byte>(array);
} }
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength) 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. // 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 multiplier = 1;
@@ -106,35 +81,33 @@ namespace MQTTnet.AspNetCore
var index = 1; var index = 1;
headerLength = 0; headerLength = 0;
bodyLength = 0; bodyLength = 0;
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
do do
{ {
if (index == span.Length) if (index == temp.Length)
{ {
return false; return false;
} }
encodedByte = span[index]; encodedByte = temp[index];
index++; index++;
value += (byte)(encodedByte & 127) * multiplier; value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128) if (multiplier > 128 * 128 * 128)
{ {
ThrowProtocolViolationException(span, index); throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
} }
multiplier *= 128; multiplier *= 128;
} while ((encodedByte & 128) != 0); } while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index; headerLength = index;
bodyLength = value; bodyLength = value;
return true; 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,7 +1,3 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
public abstract class BaseBenchmark public abstract class BaseBenchmark
@@ -2,25 +2,54 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using System; using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
[MemoryDiagnoser] [MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark : BaseBenchmark public sealed class ChannelAdapterBenchmark
{ {
MqttChannelAdapter _channelAdapter; MqttChannelAdapter _channelAdapter;
int _iterations; int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream; 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());
}
[Benchmark] [Benchmark]
public void Receive_10000_Messages() public void Receive_10000_Messages()
@@ -48,34 +77,6 @@ namespace MQTTnet.Benchmarks
_stream.Position = 0; _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, new MqttNetEventLogger());
}
static byte[] Join(params ArraySegment<byte>[] chunks) static byte[] Join(params ArraySegment<byte>[] chunks)
{ {
var buffer = new MemoryStream(); var buffer = new MemoryStream();
@@ -87,4 +88,4 @@ namespace MQTTnet.Benchmarks
return buffer.ToArray(); return buffer.ToArray();
} }
} }
} }
@@ -0,0 +1,22 @@
// 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);
}
}
}
@@ -0,0 +1,19 @@
// 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
}
}
}
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.CsProj;
namespace MQTTnet.Benchmarks.Configurations
{
public class RuntimeCompareConfig : BaseConfig
{
public RuntimeCompareConfig()
{
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter] [RPlotExporter]
[MemoryDiagnoser] [MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark public class LoggerBenchmark
{ {
MqttNetNullLogger _nullLogger; MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger; MqttNetSourceLogger _sourceNullLogger;
@@ -1,16 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<LangVersion>7.2</LangVersion> <LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -26,12 +24,11 @@
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" /> <PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" /> <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" /> <ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
</ItemGroup> </ItemGroup>
@@ -1,40 +0,0 @@
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,8 +1,5 @@
// 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.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Server; using MQTTnet.Server;
using System; using System;
@@ -11,10 +8,14 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
/// <summary>
/// Create a number of topics, publish, subscribe, and wait for response
/// </summary>
[MemoryDiagnoser] [MemoryDiagnoser]
public class MessageDeliveryBenchmark : BaseBenchmark public class MessageDeliveryBenchmark
{ {
List<MqttApplicationMessage> _topicPublishMessages; List<MqttApplicationMessage> _topicPublishMessages;
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn] [RPlotExporter, RankColumn]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MessageProcessingBenchmark : BaseBenchmark public class MessageProcessingBenchmark
{ {
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttClient _mqttClient; IMqttClient _mqttClient;
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{ {
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark public class MessageProcessingMqttConnectionContextBenchmark
{ {
IWebHost _host; IWebHost _host;
IMqttClient _mqttClient; IMqttClient _mqttClient;
@@ -1,43 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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,10 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System; using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
@@ -12,7 +9,7 @@ namespace MQTTnet.Benchmarks
{ {
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark public class MqttPacketReaderWriterBenchmark
{ {
readonly byte[] _demoPayload = new byte[1024]; readonly byte[] _demoPayload = new byte[1024];
@@ -30,17 +27,17 @@ namespace MQTTnet.Benchmarks
var writer = new MqttBufferWriter(4096, 65535); var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload); writer.WriteBinaryData(_demoPayload);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574489);
writer.WriteBinary(_demoPayload); writer.WriteBinaryData(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload); writer.WriteBinaryData(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); _readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
} }
@@ -83,11 +80,11 @@ namespace MQTTnet.Benchmarks
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589); writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload); writer.WriteBinaryData(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload); writer.WriteBinaryData(_demoPayload);
writer.Reset(0); writer.Reset(0);
} }
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
{ {
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MqttTcpChannelBenchmark : BaseBenchmark public sealed class MqttTcpChannelBenchmark
{ {
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttChannel _serverChannel; IMqttChannel _serverChannel;
+66 -121
View File
@@ -3,140 +3,85 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running; using BenchmarkDotNet.Running;
using MQTTnet.Benchmarks.Configurations;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
public static class Program public static class Program
{ {
static int _selectedBenchmarkIndex; public static void Main(string[] args)
static List<Type> _benchmarks;
public static void Main(string[] arguments)
{ {
_benchmarks = CollectBenchmarks(); Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
HandleArguments(arguments); 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");
while (true) var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
{ {
RenderMenu(); case '1':
BenchmarkRunner.Run<MessageProcessingBenchmark>();
var key = Console.ReadKey(true); break;
case '2':
if (key.Key == ConsoleKey.DownArrow) BenchmarkRunner.Run<SerializerBenchmark>();
{ break;
_selectedBenchmarkIndex++; case '3':
if (_selectedBenchmarkIndex > _benchmarks.Count - 1) BenchmarkRunner.Run<LoggerBenchmark>();
{ break;
_selectedBenchmarkIndex = 0; case '4':
} BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
} break;
else if (key.Key == ConsoleKey.UpArrow) case '5':
{ BenchmarkRunner.Run<ChannelAdapterBenchmark>();
_selectedBenchmarkIndex--; break;
if (_selectedBenchmarkIndex < 0) case '6':
{ BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
_selectedBenchmarkIndex = _benchmarks.Count - 1; break;
} case '7':
} BenchmarkRunner.Run<TcpPipesBenchmark>();
else if (key.Key == ConsoleKey.Escape) break;
{ case '8':
Environment.Exit(0); BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
} break;
else if (key.Key == ConsoleKey.Enter) 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>();
break; break;
}
} }
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine(); 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();
}
} }
} }
@@ -1,240 +0,0 @@
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)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn] [RPlotExporter, RankColumn]
[MemoryDiagnoser] [MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark public class RoundtripProcessingBenchmark
{ {
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
@@ -1,68 +0,0 @@
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);
}
}
}
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
public void Deserialize_10000_Messages() public void Deserialize_10000_Messages()
{ {
var channel = new BenchmarkMqttChannel(_serializedPacket); var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
@@ -1,7 +1,3 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
@@ -12,7 +8,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn] [RPlotExporter, RankColumn]
[MemoryDiagnoser] [MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark public class ServerProcessingBenchmark
{ {
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
@@ -1,7 +1,3 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Client; using MQTTnet.Client;
@@ -12,7 +8,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
[MemoryDiagnoser] [MemoryDiagnoser]
public class SubscribeBenchmark : BaseBenchmark public class SubscribeBenchmark
{ {
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttClient _mqttClient; IMqttClient _mqttClient;
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{ {
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class TcpPipesBenchmark : BaseBenchmark public class TcpPipesBenchmark
{ {
IDuplexPipe _client; IDuplexPipe _client;
IDuplexPipe _server; IDuplexPipe _server;
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter] [RPlotExporter]
[MemoryDiagnoser] [MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark public class TopicFilterComparerBenchmark
{ {
static readonly char[] TopicLevelSeparator = { '/' }; static readonly char[] TopicLevelSeparator = { '/' };
+15 -18
View File
@@ -1,30 +1,31 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
public class TopicGenerator public class TopicGenerator
{ {
public static void Generate( public static void Generate(
int numPublishers, int numPublishers, int numTopicsPerPublisher,
int numTopicsPerPublisher, out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> topicsByPublisher, out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher, out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher) )
{ {
topicsByPublisher = new Dictionary<string, List<string>>(); topicsByPublisher = new Dictionary<string, List<string>>();
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>(); singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>(); multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels // 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) if (topicsPerLevel <= 0)
{ {
topicsPerLevel = 1; topicsPerLevel = 1;
} }
var numLevel1Topics = topicsPerLevel; int numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel; int numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0) if (maxNumLevel3Topics <= 0)
@@ -34,7 +35,7 @@ namespace MQTTnet.Benchmarks
for (var p = 0; p < numPublishers; ++p) for (var p = 0; p < numPublishers; ++p)
{ {
var publisherTopicCount = 0; int publisherTopicCount = 0;
var publisherName = "pub" + p; var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1) for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{ {
@@ -43,22 +44,19 @@ namespace MQTTnet.Benchmarks
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{ {
if (publisherTopicCount >= numTopicsPerPublisher) if (publisherTopicCount >= numTopicsPerPublisher)
{
break; break;
}
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}"; var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
AddPublisherTopic(publisherName, topic, topicsByPublisher); AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0) if (l2 == 0)
{ {
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}"; var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
} }
if ((l1 == 0) && (l3 == 0))
if (l1 == 0 && l3 == 0)
{ {
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+"; var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
} }
@@ -77,8 +75,7 @@ namespace MQTTnet.Benchmarks
topicList = new List<string>(); topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList); topicsByPublisher.Add(publisherName, topicList);
} }
topicList.Add(topic); topicList.Add(topic);
} }
} }
} }
@@ -1,7 +1,3 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Server; using MQTTnet.Server;
@@ -11,7 +7,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
[MemoryDiagnoser] [MemoryDiagnoser]
public class UnsubscribeBenchmark : BaseBenchmark public class UnsubscribeBenchmark
{ {
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttClient _mqttClient; IMqttClient _mqttClient;
+54
View File
@@ -0,0 +1,54 @@
<?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,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName> <AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
@@ -33,7 +33,7 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime; return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
} }
async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions) async Task HandleSubscriptionExceptionAsync(Exception exception)
{ {
_logger.Warning(exception, "Synchronizing subscriptions failed."); _logger.Warning(exception, "Synchronizing subscriptions failed.");
if (_synchronizingSubscriptionsFailedEvent.HasHandlers) if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
{ {
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false); await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
} }
} }
@@ -457,13 +457,11 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
_logger.Info("Publishing subscriptions at reconnect"); _logger.Info("Publishing subscriptions at reconnect");
List<MqttTopicFilter> topicFilters = null;
try try
{ {
if (_reconnectSubscriptions.Any()) if (_reconnectSubscriptions.Any())
{ {
topicFilters = new List<MqttTopicFilter>(); var topicFilters = new List<MqttTopicFilter>();
foreach (var sub in _reconnectSubscriptions) foreach (var sub in _reconnectSubscriptions)
{ {
@@ -481,7 +479,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
} }
} }
@@ -593,9 +591,6 @@ namespace MQTTnet.Extensions.ManagedClient
} }
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false); await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
} }
if (addedSubscriptions != null && addedSubscriptions.Any()) if (addedSubscriptions != null && addedSubscriptions.Any())
@@ -612,7 +607,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
} }
} }
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
/// <summary> /// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet. /// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue. /// Amazon AWS limits this number to 8. The default is int.MaxValue.
/// </summary> /// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue; public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
} }
@@ -2,33 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using MQTTnet.Packets;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public class ManagedProcessFailedEventArgs : EventArgs public class ManagedProcessFailedEventArgs : EventArgs
{ {
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions) public ManagedProcessFailedEventArgs(Exception exception)
{ {
Exception = exception ?? throw new ArgumentNullException(nameof(exception)); Exception = exception ?? throw new ArgumentNullException(nameof(exception));
if (addedSubscriptions != null)
{
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
}
if (removedSubscriptions != null)
{
RemovedSubscriptions = new List<string>(removedSubscriptions);
}
} }
public Exception Exception { get; } public Exception Exception { get; }
public List<string> AddedSubscriptions { get; }
public List<string> RemovedSubscriptions { get; }
} }
} }
@@ -12,37 +12,22 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null) public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{ {
if (factory == null) if (factory == null) throw new ArgumentNullException(nameof(factory));
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null) if (mqttClient == null)
{ {
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger); return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
} }
return new ManagedMqttClient(mqttClient, factory.DefaultLogger); return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
} }
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger) public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{ {
if (factory == null) if (factory == null) throw new ArgumentNullException(nameof(factory));
{ if (logger == null) throw new ArgumentNullException(nameof(logger));
throw new ArgumentNullException(nameof(factory));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger); return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
} }
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
{
return new ManagedMqttClientOptionsBuilder();
}
} }
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -8,8 +7,8 @@ namespace MQTTnet.Extensions.Rpc
{ {
public interface IMqttRpcClient : IDisposable public interface IMqttRpcClient : IDisposable
{ {
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null); Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default); Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default);
} }
} }
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName> <AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
@@ -33,8 +33,7 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -11,12 +11,10 @@ namespace MQTTnet.Extensions.Rpc
{ {
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient) public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
{ {
return factory.CreateMqttRpcClient( return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
mqttClient, {
new MqttRpcClientOptions TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
{ });
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
} }
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
+52 -50
View File
@@ -2,27 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc
{ {
public sealed class MqttRpcClient : IMqttRpcClient public sealed class MqttRpcClient : IMqttRpcClient
{ {
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
readonly IMqttClient _mqttClient; readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options; readonly MqttRpcClientOptions _options;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options) public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{ {
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -31,25 +27,13 @@ namespace MQTTnet.Extensions.Rpc
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync; _mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
} }
public void Dispose() public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
_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, IDictionary<string, object> parameters = null)
{ {
using (var timeoutToken = new CancellationTokenSource(timeout)) using (var timeoutToken = new CancellationTokenSource(timeout))
{ {
try try
{ {
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false); return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false);
} }
catch (OperationCanceledException exception) catch (OperationCanceledException exception)
{ {
@@ -63,15 +47,17 @@ namespace MQTTnet.Extensions.Rpc
} }
} }
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default) public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
{ {
if (methodName == null) if (methodName == null) throw new ArgumentNullException(nameof(methodName));
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
{ {
throw new ArgumentNullException(nameof(methodName)); MethodName = methodName,
} QualityOfServiceLevel = qualityOfServiceLevel,
MqttClient = _mqttClient,
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel); Options = _options
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); });
var requestTopic = topicNames.RequestTopic; var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic; var responseTopic = topicNames.ResponseTopic;
@@ -86,34 +72,34 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty."); throw new MqttProtocolViolationException("RPC response topic is empty.");
} }
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel); var requestMessage = new MqttApplicationMessageBuilder()
.WithTopic(requestTopic)
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500) .WithPayload(payload)
{ .WithQualityOfServiceLevel(qualityOfServiceLevel)
requestMessageBuilder.WithResponseTopic(responseTopic); .WithResponseTopic(responseTopic)
} .Build();
var requestMessage = requestMessageBuilder.Build();
try try
{ {
var awaitable = new AsyncTaskCompletionSource<byte[]>(); #if NET452
var awaitable = new TaskCompletionSource<byte[]>();
#else
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
#endif
if (!_waitingCalls.TryAdd(responseTopic, awaitable)) if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{ {
throw new InvalidOperationException(); 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.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false); await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register( using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
() =>
{
awaitable.TrySetCanceled();
}))
{ {
return await awaitable.Task.ConfigureAwait(false); return await awaitable.Task.ConfigureAwait(false);
} }
@@ -121,7 +107,8 @@ namespace MQTTnet.Extensions.Rpc
finally finally
{ {
_waitingCalls.TryRemove(responseTopic, out _); _waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
} }
} }
@@ -132,13 +119,28 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance; return CompletedTask.Instance;
} }
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray(); #if NET452
awaitable.TrySetResult(payloadBuffer); 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. // Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true; eventArgs.IsHandled = true;
return CompletedTask.Instance; return CompletedTask.Instance;
} }
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
}
} }
} }
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -12,13 +11,13 @@ namespace MQTTnet.Extensions.Rpc
{ {
public static class MqttRpcClientExtensions public static class MqttRpcClientExtensions
{ {
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null) public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client == null) throw new ArgumentNullException(nameof(client));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters); return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel);
} }
} }
} }
@@ -10,6 +10,13 @@ namespace MQTTnet.Extensions.Rpc
{ {
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy(); IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public MqttRpcClientOptions Build() public MqttRpcClientOptions Build()
{ {
return new MqttRpcClientOptions return new MqttRpcClientOptions
@@ -17,12 +24,5 @@ namespace MQTTnet.Extensions.Rpc
TopicGenerationStrategy = _topicGenerationStrategy TopicGenerationStrategy = _topicGenerationStrategy
}; };
} }
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
} }
} }
@@ -10,11 +10,6 @@ namespace MQTTnet.Extensions.Rpc
{ {
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{ {
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#")) if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{ {
throw new ArgumentException("The method name cannot contain /, + or #."); throw new ArgumentException("The method name cannot contain /, + or #.");
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -11,33 +9,20 @@ namespace MQTTnet.Extensions.Rpc
{ {
public sealed class TopicGenerationContext public sealed class TopicGenerationContext
{ {
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, public string MethodName { get; set; }
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
/// <summary> /// <summary>
/// Gets or sets the quality of service level. /// 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 /// 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.
/// that defines the guarantee of delivery for a specific message. /// There are 3 QoS levels in MQTT:
/// There are 3 QoS levels in MQTT: /// - At most once (0): Message gets delivered no time, once or multiple times.
/// - 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).
/// - 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).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary> /// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public IMqttClient MqttClient { get; set; }
public MqttRpcClientOptions Options { get; set; }
} }
} }
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName> <AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -33,8 +33,7 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -10,12 +10,9 @@ namespace MQTTnet.Extensions.WebSocket4Net
{ {
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory) public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
{ {
if (mqttFactory == null) if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
{
throw new ArgumentNullException(nameof(mqttFactory));
}
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory()); return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
} }
} }
} }
@@ -5,7 +5,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Security.Authentication; using System.Security.Authentication;
@@ -18,14 +17,14 @@ using MQTTnet.Exceptions;
using MQTTnet.Internal; using MQTTnet.Internal;
using SuperSocket.ClientEngine; using SuperSocket.ClientEngine;
using WebSocket4Net; using WebSocket4Net;
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
namespace MQTTnet.Extensions.WebSocket4Net namespace MQTTnet.Extensions.WebSocket4Net
{ {
public sealed class WebSocket4NetMqttChannel : IMqttChannel public sealed class WebSocket4NetMqttChannel : IMqttChannel
{ {
readonly MqttClientOptions _clientOptions;
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>(); readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
readonly MqttClientOptions _clientOptions;
readonly MqttClientWebSocketOptions _webSocketOptions; readonly MqttClientWebSocketOptions _webSocketOptions;
WebSocket _webSocket; WebSocket _webSocket;
@@ -36,13 +35,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions)); _webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
} }
public X509Certificate2 ClientCertificate { get; }
public string Endpoint => _webSocketOptions.Uri; public string Endpoint => _webSocketOptions.Uri;
public bool IsSecureConnection { get; private set; } public bool IsSecureConnection { get; private set; }
public Task ConnectAsync(CancellationToken cancellationToken) public X509Certificate2 ClientCertificate { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
var uri = _webSocketOptions.Uri; var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
@@ -56,13 +55,11 @@ namespace MQTTnet.Extensions.WebSocket4Net
uri = "wss://" + uri; uri = "wss://" + uri;
} }
} }
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
#if NET48 || NETCOREAPP3_0_OR_GREATER #if NET48 || NETCOREAPP3_1 || NET5 || NET6
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13; var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
#else #else
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/; var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif #endif
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty; var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
@@ -92,20 +89,33 @@ namespace MQTTnet.Extensions.WebSocket4Net
var webSocketVersion = WebSocketVersion.None; var webSocketVersion = WebSocketVersion.None;
var receiveBufferSize = 0; var receiveBufferSize = 0;
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates(); var certificates = new X509CertificateCollection();
if (_webSocketOptions?.TlsOptions?.Certificates != null)
{
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
{
#if WINDOWS_UWP
certificates.Add(new X509Certificate(certificate));
#else
certificates.Add(certificate);
#endif
}
}
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize) _webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
{ {
NoDelay = true, NoDelay = true,
Security = Security =
{ {
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true, AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true, AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
Certificates = certificates Certificates = certificates
} }
}; };
return ConnectInternalAsync(cancellationToken); await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
} }
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -114,23 +124,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
{ {
_webSocket.Close(); _webSocket.Close();
} }
return CompletedTask.Instance; 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) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
var readBytes = 0; var readBytes = 0;
@@ -164,55 +161,75 @@ namespace MQTTnet.Extensions.WebSocket4Net
return CompletedTask.Instance; 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) async Task ConnectInternalAsync(CancellationToken cancellationToken)
{ {
_webSocket.Error += OnError; _webSocket.Error += OnError;
_webSocket.DataReceived += OnDataReceived; _webSocket.DataReceived += OnDataReceived;
var promise = new AsyncTaskCompletionSource<bool>(); var taskCompletionSource = new TaskCompletionSource<Exception>();
void ErrorHandler(object sender, ErrorEventArgs e) void ErrorHandler(object sender, ErrorEventArgs e)
{ {
promise.TrySetException(e.Exception); taskCompletionSource.TrySetResult(e.Exception);
} }
void OpenedHandler(object sender, EventArgs e) void SuccessHandler(object sender, EventArgs e)
{ {
promise.TrySetResult(true); taskCompletionSource.TrySetResult(null);
}
void ClosedHandler(object sender, EventArgs e)
{
promise.TrySetException(new MqttCommunicationException("Connection closed."));
} }
try try
{ {
_webSocket.Opened += OpenedHandler; _webSocket.Opened += SuccessHandler;
_webSocket.Closed += ClosedHandler;
_webSocket.Error += ErrorHandler; _webSocket.Error += ErrorHandler;
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout)) #pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) _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 (linkedCts.Token.Register(() => promise.TrySetCanceled())) using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
{ {
try try
{ {
// OpenAsync does basically the same but without support for a cancellation token. await taskCompletionSource.Task.ConfigureAwait(false);
// So we do it on our own!
_webSocket.Open();
await promise.Task.ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested; var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached) if (timeoutReached)
{ {
throw new MqttCommunicationTimedOutException(exception); throw new MqttCommunicationTimedOutException(exception);
} }
if (exception is AuthenticationException authenticationException) if (exception is AuthenticationException authenticationException)
{ {
throw new MqttCommunicationException(authenticationException.InnerException); throw new MqttCommunicationException(authenticationException.InnerException);
@@ -234,23 +251,9 @@ namespace MQTTnet.Extensions.WebSocket4Net
} }
finally finally
{ {
_webSocket.Opened -= OpenedHandler; _webSocket.Opened -= SuccessHandler;
_webSocket.Closed -= ClosedHandler;
_webSocket.Error -= ErrorHandler; _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. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using System;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.Extensions.WebSocket4Net namespace MQTTnet.Extensions.WebSocket4Net
{ {
@@ -15,10 +15,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
{ {
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger) public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{ {
if (options == null) if (options == null) throw new ArgumentNullException(nameof(options));
{
throw new ArgumentNullException(nameof(options));
}
switch (options.ChannelOptions) switch (options.ChannelOptions)
{ {
@@ -26,29 +23,25 @@ namespace MQTTnet.Extensions.WebSocket4Net
{ {
return new MqttChannelAdapter( return new MqttChannelAdapter(
new MqttTcpChannel(options), new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
logger) packetInspector,
{ logger);
PacketInspector = packetInspector
};
} }
case MqttClientWebSocketOptions webSocketOptions: case MqttClientWebSocketOptions webSocketOptions:
{ {
return new MqttChannelAdapter( return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions), new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
logger) packetInspector,
{ logger);
PacketInspector = packetInspector
};
} }
default: default:
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }
} }
} }
} }
+1 -10
View File
@@ -33,18 +33,9 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
var payloadText = string.Empty;
if (e.ApplicationMessage.PayloadSegment.Count > 0)
{
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {payloadText}"); Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Console.WriteLine();
@@ -1,19 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> <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" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+4 -4
View File
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
return new MqttApplicationMessage return new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)), Payload = Encoding.UTF8.GetBytes(Payload),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
} }
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")), Payload = Encoding.UTF8.GetBytes("Hello World"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
}; };
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")), Payload = Encoding.UTF8.GetBytes("Hello World"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")), Payload = Encoding.UTF8.GetBytes("Hello World"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };
+78 -111
View File
@@ -2,12 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using System; using System;
using System.Security.Authentication; using System.Security.Authentication;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -18,9 +17,13 @@ namespace MQTTnet.TestApp
{ {
public static async Task RunAsync() 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 #if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0 // TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientTlsOptions var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
{ {
UseTls = true, UseTls = true,
SslProtocol = SslProtocols.Tls13, SslProtocol = SslProtocols.Tls13,
@@ -28,8 +31,9 @@ namespace MQTTnet.TestApp
CertificateValidationHandler = w => true CertificateValidationHandler = w => true
}; };
#endif #endif
// Also defining TLS12 for servers that don't seem no to support TLS13. // Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientTlsOptions var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
{ {
UseTls = true, UseTls = true,
SslProtocol = SslProtocols.Tls12, SslProtocol = SslProtocols.Tls12,
@@ -38,116 +42,85 @@ namespace MQTTnet.TestApp
}; };
// mqtt.eclipseprojects.io // mqtt.eclipseprojects.io
await ExecuteTestAsync( await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
"mqtt.eclipseprojects.io TCP", new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
"mqtt.eclipseprojects.io WS", new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13", await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt")) new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
true);
#endif #endif
// test.mosquitto.org // test.mosquitto.org
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP",
"test.mosquitto.org TCP", new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
"test.mosquitto.org TCP - Authenticated", new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
"test.mosquitto.org TCP TLS12", new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
await ExecuteTestAsync("test.mosquitto.org TCP TLS13", await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif #endif
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
"test.mosquitto.org TCP TLS12 - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885) new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite") .WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311) .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
.WithTlsOptions(unsafeTls12)
.Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org WS",
"test.mosquitto.org WS", new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org WS TLS12",
"test.mosquitto.org WS (WebSocket4Net)", new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(), .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
true);
await ExecuteTestAsync(
"test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(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 // broker.emqx.io
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io TCP",
"broker.emqx.io TCP", new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io TCP TLS12",
"broker.emqx.io TCP TLS12", new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
await ExecuteTestAsync("broker.emqx.io TCP TLS13", await ExecuteTestAsync("broker.emqx.io TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883) new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif #endif
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io WS",
"broker.emqx.io WS", new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io WS TLS12",
"broker.emqx.io WS (WebSocket4Net)", new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(), .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
true);
await ExecuteTestAsync(
"broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
true);
// broker.hivemq.com // broker.hivemq.com
await ExecuteTestAsync( await ExecuteTestAsync("broker.hivemq.com TCP",
"broker.hivemq.com TCP", new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("broker.hivemq.com WS",
"broker.hivemq.com WS", new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
// mqtt.swifitch.cz: Does not seem to operate any more // mqtt.swifitch.cz: Does not seem to operate any more
@@ -157,43 +130,36 @@ namespace MQTTnet.TestApp
Console.ReadLine(); Console.ReadLine();
} }
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false) static async Task ExecuteTestAsync(string name, MqttClientOptions options)
{ {
try try
{ {
Write("Testing '" + name + "'... ", ConsoleColor.Gray); Write("Testing '" + name + "'... ", ConsoleColor.Gray);
var factory = new MqttFactory(); var factory = new MqttFactory();
if (useWebSocket4Net) //factory.UseWebSocket4Net();
var client = factory.CreateMqttClient();
var topic = Guid.NewGuid().ToString();
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceivedAsync += e =>
{ {
factory.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.");
} }
using (var client = factory.CreateMqttClient()) await client.UnsubscribeAsync(topic);
{ await client.DisconnectAsync();
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); Write("[OK]\n", ConsoleColor.Green);
} }
@@ -208,5 +174,6 @@ namespace MQTTnet.TestApp
Console.ForegroundColor = color; Console.ForegroundColor = color;
Console.Write(message); Console.Write(message);
} }
} }
} }
+4 -11
View File
@@ -5,7 +5,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
@@ -101,7 +100,7 @@ namespace MQTTnet.TestApp
{ {
// Replace the payload with the timestamp. But also extending a JSON // Replace the payload with the timestamp. But also extending a JSON
// based payload with the timestamp is a suitable use case. // based payload with the timestamp is a suitable use case.
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
} }
if (e.ApplicationMessage.Topic == "not_allowed_topic") if (e.ApplicationMessage.Topic == "not_allowed_topic")
@@ -144,16 +143,10 @@ namespace MQTTnet.TestApp
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
{ {
var payloadText = string.Empty; MqttNetConsoleLogger.PrintToConsole(
if (e.ApplicationMessage.PayloadSegment.Count > 0) $"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
{ ConsoleColor.Magenta);
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Clients.LowLevelMqttClient namespace MQTTnet.Tests.Client
{ {
[TestClass] [TestClass]
public sealed class LowLevelMqttClient_Tests : BaseTestClass public sealed class LowLevelMqttClient_Tests : BaseTestClass
@@ -17,43 +17,11 @@ using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Clients.ManagedMqttClient namespace MQTTnet.Tests.Client
{ {
[TestClass] [TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
{ {
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod] [TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected() public async Task Receive_While_Not_Cleanly_Disconnected()
{ {
@@ -79,12 +47,10 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.StartAsync(managedClientOptions); await managedClient.StartAsync(managedClientOptions);
await LongTestDelay(); await LongTestDelay();
await LongTestDelay();
// Send test data. // Send test data.
await senderClient.PublishStringAsync("topic1"); await senderClient.PublishStringAsync("topic1");
await LongTestDelay(); await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1); receivedMessages.AssertReceivedCountEquals(1);
@@ -196,7 +162,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
.Build(); .Build();
var dyingClient = testEnvironment.CreateClient(); var dyingClient = testEnvironment.CreateClient();
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger); var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build()); await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
@@ -231,7 +197,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger()); var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
@@ -258,7 +224,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger()); var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage(); var storage = new ManagedMqttClientTestStorage();
@@ -363,7 +329,13 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await testEnvironment.StartServer().ConfigureAwait(false); await testEnvironment.StartServer().ConfigureAwait(false);
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false); var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
await sendingClient.PublishStringAsync("topic", "A", retain: true); await sendingClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "topic",
Payload = new byte[] { 1 },
Retain = true
});
// Wait a bit for the retained message to be available // Wait a bit for the retained message to be available
await LongTestDelay(); await LongTestDelay();
@@ -385,15 +357,18 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await LongTestDelay(); await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count); Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync(); await managedClient.StopAsync();
await LongTestDelay(); await LongTestDelay();
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await LongTestDelay(); await LongTestDelay();
// After reconnect and then some delay, the retained message must not be received, // After reconnect and then some delay, the retained message must not be received,
@@ -402,6 +377,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
// Make sure that it gets received after subscribing again. // Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await LongTestDelay(); await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count); Assert.AreEqual(2, receivedManagedMessages.Count);
@@ -421,7 +397,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient(); var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>( new byte[] { 1 }), Retain = true }); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var subscribeTime = DateTime.UtcNow; var subscribeTime = DateTime.UtcNow;
@@ -454,7 +430,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
//wait a bit for the subscription to become established //wait a bit for the subscription to become established
await Task.Delay(500); await Task.Delay(500);
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true }); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var messages = await SetupReceivingOfMessages(managedClient, 1); var messages = await SetupReceivingOfMessages(managedClient, 1);
@@ -471,7 +447,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
} }
} }
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync( async Task<ManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment, TestEnvironment testEnvironment,
IMqttClient underlyingClient = null, IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null, TimeSpan? connectionCheckInterval = null,
@@ -488,7 +464,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
// at connection check intervals // at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1); managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger()); var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
@@ -505,7 +481,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
/// name="managedClient" /> /// name="managedClient" />
/// has connected /// has connected
/// </summary> /// </summary>
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient) Task GetConnectedTask(ManagedMqttClient managedClient)
{ {
var connected = new TaskCompletionSource<bool>(); var connected = new TaskCompletionSource<bool>();
@@ -527,7 +503,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
/// name="expectedNumberOfMessages" /> /// name="expectedNumberOfMessages" />
/// have been received /// have been received
/// </summary> /// </summary>
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages) Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
{ {
var receivedMessages = new List<MqttApplicationMessage>(); var receivedMessages = new List<MqttApplicationMessage>();
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>(); var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -6,8 +6,9 @@ using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions;
namespace MQTTnet.Tests.Clients namespace MQTTnet.Tests.Client
{ {
[TestClass] [TestClass]
public class MqttClientOptionsBuilder_Tests public class MqttClientOptionsBuilder_Tests
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Clients.MqttClient namespace MQTTnet.Tests.Client
{ {
[TestClass] [TestClass]
public sealed class MqttClient_Tests : BaseTestClass public sealed class MqttClient_Tests : BaseTestClass
@@ -71,34 +71,60 @@ namespace MQTTnet.Tests.Clients.MqttClient
} }
[TestMethod] [TestMethod]
public async Task Connect_Disconnect_Connect() [ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{ {
using (var testEnvironment = CreateTestEnvironment()) 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)))
{ {
await testEnvironment.StartServer(); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.DisconnectAsync();
await client.ConnectAsync(clientOptions);
} }
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(InvalidOperationException))] [ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_Multiple_Times_Should_Fail() public async Task Connect_To_Invalid_Server_Wrong_Protocol()
{ {
using (var testEnvironment = CreateTestEnvironment()) 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())
{ {
await testEnvironment.StartServer(); var disconnectHandlerCalled = false;
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.CreateDefaultClientOptions(); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions); Assert.Fail("Must fail!");
await client.ConnectAsync(clientOptions); }
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);
} }
} }
@@ -296,7 +322,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.IsNotNull(receivedMessage); Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic); Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array); Assert.AreEqual(null, receivedMessage.Payload);
} }
} }
@@ -507,7 +533,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
client2.ApplicationMessageReceivedAsync += e => client2.ApplicationMessageReceivedAsync += e =>
{ {
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray())); client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -1,222 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
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);
await LongTestDelay();
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);
await LongTestDelay();
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);
await LongTestDelay();
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);
}
}
[TestMethod]
public async Task Return_Non_Success()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ResponseUserProperties = new List<MqttUserProperty>
{
new MqttUserProperty("Property", "Value")
};
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
Assert.IsNotNull(response);
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
Assert.AreEqual(response.UserProperties[0].Name, "Property");
Assert.AreEqual(response.UserProperties[0].Value, "Value");
}
}
[TestMethod]
public async Task Throw_Proper_Exception_When_Not_Connected()
{
try
{
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
}
}
catch (MqttCommunicationException exception)
{
if (exception.Message == "The client is not connected.")
{
return;
}
}
Assert.Fail();
}
}
}
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
var packets = new List<string>(); var packets = new List<string>();
mqttClient.InspectPacketAsync += eventArgs => mqttClient.InspectPackage += eventArgs =>
{ {
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return CompletedTask.Instance; return CompletedTask.Instance;
+98
View File
@@ -0,0 +1,98 @@
// 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 System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
namespace MQTTnet.Tests
{
[TestClass]
public class Extension_Tests
{
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfter()
{
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfterWithResult()
{
await MqttTaskTimeout.WaitAsync(
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[TestMethod]
public async Task TimeoutAfterWithInnerException()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterWithInnerExceptionWithResult()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
return iis[0];
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterMemoryUsage()
{
var initialMemory = GC.GetTotalMemory(true);
var tasks = Enumerable.Range(0, 100000)
.Select(i =>
{
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
TimeSpan.FromMinutes(1), CancellationToken.None);
});
await Task.WhenAll(tasks);
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
}
static void AssertIsLess(long bound, long actual)
{
if (bound < actual)
{
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
}
}
}
}
@@ -5,7 +5,7 @@
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Helpers namespace MQTTnet.Tests.Extensions
{ {
public static class MqttPacketWriterExtensions public static class MqttPacketWriterExtensions
{ {
@@ -1,51 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.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(o => o.WithUri("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();
}
}
}
@@ -1,310 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.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.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Helpers; using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Formatter namespace MQTTnet.Tests.Formatter
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")), Payload = Encoding.ASCII.GetBytes("HELLO"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -318,9 +318,10 @@ namespace MQTTnet.Tests.Formatter
var publishPacket = new MqttPublishPacket var publishPacket = new MqttPublishPacket
{ {
Topic = "abcdefghijklmnopqrstuvwxyz0123456789", Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
PayloadSegment = new ArraySegment<byte>(payload) Payload = payload
}; };
var serializationHelper = new MqttPacketSerializationHelper(); var serializationHelper = new MqttPacketSerializationHelper();
var buffer = serializationHelper.Encode(publishPacket); var buffer = serializationHelper.Encode(publishPacket);
@@ -328,17 +329,17 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNotNull(publishPacketCopy); Assert.IsNotNull(publishPacketCopy);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray()); CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload);
// Now modify the payload and test again. // Now modify the payload and test again.
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT")); publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
buffer = serializationHelper.Encode(publishPacket); buffer = serializationHelper.Encode(publishPacket);
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
Assert.IsNotNull(publishPacketCopy2); Assert.IsNotNull(publishPacketCopy2);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray()); CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
} }
[TestMethod] [TestMethod]
@@ -462,7 +463,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")), Payload = Encoding.ASCII.GetBytes("HELLO"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -566,6 +567,7 @@ namespace MQTTnet.Tests.Formatter
using (var adapter = new MqttChannelAdapter( using (var adapter = new MqttChannelAdapter(
channel, channel,
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
null,
new MqttNetEventLogger())) new MqttNetEventLogger()))
{ {
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -581,12 +583,19 @@ namespace MQTTnet.Tests.Formatter
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer) MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
{ {
var channel = new MemoryMqttChannel(buffer); var channel = new MemoryMqttChannel(buffer);
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
return packetFormatterAdapter.ProtocolVersion; return packetFormatterAdapter.ProtocolVersion;
} }
MqttBufferReader ReaderFactory(byte[] data)
{
var reader = new MqttBufferReader();
reader.SetBuffer(data, 0, data.Length);
return reader;
}
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
{ {
var writer = bufferWriter ?? WriterFactory(); var writer = bufferWriter ?? WriterFactory();
@@ -595,7 +604,7 @@ namespace MQTTnet.Tests.Formatter
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray())) using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
{ {
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -78,7 +76,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
} }
[TestMethod] [TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310() public void Serialize_Full_MqttConnAckPacket_V310()
{ {
var connAckPacket = new MqttConnAckPacket var connAckPacket = new MqttConnAckPacket
@@ -298,7 +296,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")), Payload = Encoding.ASCII.GetBytes("Payload"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -322,7 +320,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray()); CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
@@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -259,7 +257,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")), Payload = Encoding.ASCII.GetBytes("Payload"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -283,7 +281,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray()); CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
@@ -1,7 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter namespace MQTTnet.Tests.Formatter
@@ -48,32 +47,23 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(1234U, reader.ReadVariableByteInteger()); Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
Assert.AreEqual(9876U, reader.ReadVariableByteInteger()); Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
} }
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Throw_If_String_Too_Long()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString(string.Empty.PadLeft(65536));
}
[TestMethod] [TestMethod]
public void Write_And_Read_Multiple_Times() public void Write_And_Read_Multiple_Times()
{ {
var writer = new MqttBufferWriter(4096, 65535); var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinary(new byte[1234]); writer.WriteBinaryData(new byte[1234]);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574489);
writer.WriteBinary(new byte[48]); writer.WriteBinaryData(new byte[48]);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(new byte[3]); writer.WriteBinaryData(new byte[3]);
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
@@ -1,23 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using 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,28 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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);
}
}
}
+5 -4
View File
@@ -1,26 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" 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.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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" /> <ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" /> <ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" /> <ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+2 -2
View File
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
var c2 = await testEnvironment.ConnectClient(clientOptions); var c2 = await testEnvironment.ConnectClient(clientOptions);
c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent. c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
await LongTestDelay(); await Task.Delay(1000);
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
} }
@@ -27,17 +27,6 @@ namespace MQTTnet.Tests.Mockups
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync; mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
} }
public int Count
{
get
{
lock (_receivedEventArgs)
{
return _receivedEventArgs.Count;
}
}
}
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
{ {
get get
@@ -51,7 +40,10 @@ namespace MQTTnet.Tests.Mockups
public void AssertReceivedCountEquals(int expectedCount) public void AssertReceivedCountEquals(int expectedCount)
{ {
Assert.AreEqual(expectedCount, Count); lock (_receivedEventArgs)
{
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
}
} }
public string GeneratePayloadSequence() public string GeneratePayloadSequence()
+34 -44
View File
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -25,9 +24,10 @@ namespace MQTTnet.Tests.Mockups
public sealed class TestEnvironment : IDisposable public sealed class TestEnvironment : IDisposable
{ {
readonly List<string> _clientErrors = new List<string>(); readonly List<string> _clientErrors = new List<string>();
readonly ConcurrentBag<IMqttClient> _clients = new ConcurrentBag<IMqttClient>(); readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>(); readonly List<Exception> _exceptions = new List<Exception>();
readonly ConcurrentBag<ILowLevelMqttClient> _lowLevelClients = new ConcurrentBag<ILowLevelMqttClient>(); readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly MqttProtocolVersion _protocolVersion; readonly MqttProtocolVersion _protocolVersion;
readonly List<string> _serverErrors = new List<string>(); readonly List<string> _serverErrors = new List<string>();
@@ -65,12 +65,9 @@ namespace MQTTnet.Tests.Mockups
if (e.LogMessage.Level == MqttNetLogLevel.Error) if (e.LogMessage.Level == MqttNetLogLevel.Error)
{ {
if (!IgnoreClientLogErrors) lock (_clientErrors)
{ {
lock (_clientErrors) _clientErrors.Add(e.LogMessage.ToString());
{
_clientErrors.Add(e.LogMessage.ToString());
}
} }
} }
}; };
@@ -215,45 +212,41 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient() public IMqttClient CreateClient()
{ {
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance; lock (_clients)
var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e =>
{ {
if (TestContext != null) var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
var client = Factory.CreateMqttClient(logger);
_clients.Add(client);
client.ConnectingAsync += e =>
{ {
var clientOptions = e.ClientOptions; if (TestContext != null)
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{ {
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId; var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
} }
}
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
_clients.Add(client);
return client;
}
public MqttClientOptions CreateDefaultClientOptions() return client;
{ }
return CreateDefaultClientOptionsBuilder().Build();
}
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
} }
public ILowLevelMqttClient CreateLowLevelClient() public ILowLevelMqttClient CreateLowLevelClient()
{ {
var client = Factory.CreateLowLevelMqttClient(ClientLogger); lock (_clients)
_lowLevelClients.Add(client); {
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
return client; return client;
}
} }
public MqttServer CreateServer(MqttServerOptions options) public MqttServer CreateServer(MqttServerOptions options)
@@ -380,16 +373,13 @@ namespace MQTTnet.Tests.Mockups
} }
} }
if (!IgnoreClientLogErrors) lock (_clientErrors)
{ {
lock (_clientErrors) if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
{ {
if (_clientErrors.Count > 0) var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
{ Console.WriteLine(message);
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})"; throw new Exception(message);
Console.WriteLine(message);
throw new Exception(message);
}
} }
} }
} }
@@ -4,7 +4,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -12,7 +11,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public sealed class MqttApplicationMessageBuilder_Tests public class MqttApplicationMessageBuilder_Tests
{ {
[TestMethod] [TestMethod]
public void CreateApplicationMessage_TopicOnly() public void CreateApplicationMessage_TopicOnly()
@@ -30,7 +29,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("xyz", message.Topic); Assert.AreEqual("xyz", message.Topic);
Assert.IsFalse(message.Retain); Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00"); Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
} }
[TestMethod] [TestMethod]
@@ -42,7 +41,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("123", message.Topic); Assert.AreEqual("123", message.Topic);
Assert.IsFalse(message.Retain); Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello"); Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
} }
[TestMethod] [TestMethod]
@@ -1,48 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttApplicationMessageTest
{
[TestMethod]
public void PayloadSegment()
{
var message = new MqttApplicationMessage();
Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsFalse(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
}
}
}
@@ -1,44 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using 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);
}
}
}
@@ -1,54 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using 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();
}
}
}
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
{ {
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535)); var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance); var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
writer.WriteString("1234567890"); writer.WriteString("1234567890");
Assert.AreEqual(10 + 2, writer.Length); Assert.AreEqual(10 + 2, writer.Length);
writer.WriteBinary(new byte[300]); writer.WriteBinaryData(new byte[300]);
Assert.AreEqual(300 + 2 + 12, writer.Length); Assert.AreEqual(300 + 2 + 12, writer.Length);
writer.WriteBinary(new byte[5000]); writer.WriteBinaryData(new byte[5000]);
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length); Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
} }
} }
@@ -9,13 +9,19 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public sealed class MqttTopicValidator_Tests public class MqttTopicValidator_Tests
{ {
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))] public void Valid_Topic()
public void Invalid_Topic_Empty()
{ {
MqttTopicValidator.ThrowIfInvalid(string.Empty); MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Plus()
{
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
} }
[TestMethod] [TestMethod]
@@ -27,15 +33,9 @@ namespace MQTTnet.Tests
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))] [ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Plus() public void Invalid_Topic_Empty()
{ {
MqttTopicValidator.ThrowIfInvalid("/a/+/c"); MqttTopicValidator.ThrowIfInvalid(string.Empty);
}
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
} }
} }
} }
+6 -27
View File
@@ -8,48 +8,27 @@ using MQTTnet.Formatter;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public sealed class Protocol_Tests public class Protocol_Tests
{ {
[TestMethod] [TestMethod]
public void Encode_Four_Byte_Integer() public void Encode_Four_Byte_Integer()
{ {
var writer = new MqttBufferWriter(4, 4); var writer = new MqttBufferWriter(4, 4);
for (uint value = 0; value < 268435455; value++) for (uint value = 0; value < 268435455; value++)
{ {
writer.WriteVariableByteInteger(value); writer.WriteVariableByteInteger(value);
var buffer = writer.GetBuffer(); var buffer = writer.GetBuffer();
var reader = new MqttBufferReader(); var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, writer.Length); reader.SetBuffer(buffer, 0, writer.Length);
var checkValue = reader.ReadVariableByteInteger(); var checkValue = reader.ReadVariableByteInteger();
Assert.AreEqual(value, checkValue); 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); writer.Reset(0);
} }
} }
} }
} }
@@ -3,125 +3,71 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Tests.Mockups;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc; using MQTTnet.Extensions.Rpc;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups; using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Extensions namespace MQTTnet.Tests
{ {
[TestClass] [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 async Task Execute_Success_Parameters_Propagated_Correctly()
{
var paramValue = "123";
var parameters = new Dictionary<string, object>
{
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" },
};
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}");
responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder()
.WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_0() public Task Execute_Success_With_QoS_0()
{ {
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311); return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, 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_0_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_1() public Task Execute_Success_With_QoS_1()
{ {
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311); 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] [TestMethod]
public Task Execute_Success_With_QoS_2() public Task Execute_Success_With_QoS_2()
{ {
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311); 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] [TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5() public Task Execute_Success_With_QoS_2_MQTT_V5()
{ {
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500); 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_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic() public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
{ {
@@ -144,31 +90,6 @@ namespace MQTTnet.Tests.Extensions
} }
} }
[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] [TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))] [ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_With_Custom_Topic_Names() public async Task Execute_With_Custom_Topic_Names()
@@ -177,24 +98,14 @@ namespace MQTTnet.Tests.Extensions
{ {
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var requestSender = await testEnvironment.ConnectClient();
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build()); var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); 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) async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
@@ -240,6 +151,57 @@ namespace MQTTnet.Tests.Extensions
} }
} }
[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 class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
{ {
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
@@ -251,32 +213,5 @@ namespace MQTTnet.Tests.Extensions
}; };
} }
} }
class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{
internal const string ExpectedParamName = "test_param_name";
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
if (context.Parameters == null)
{
throw new InvalidOperationException("Parameters dictionary expected to be not null");
}
if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue))
{
throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present");
}
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
}
}
} }
} }

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