Compare commits

..
Author SHA1 Message Date
Christian de647f650c Add unit test to reproduce issue 2022-11-07 19:51:44 +01:00
Christian 5c74728387 Fix client session takeover 2022-11-06 13:28:41 +01:00
Christian 97c46bea0f Update ReleaseNotes.md 2022-11-06 10:50:32 +01:00
Christian e6c1ab4e4c Fail all waiting tasks when the client connection gets closed (#1563) 2022-11-06 10:50:04 +01:00
Christian 6e1ec266a7 Update ReleaseNotes.md 2022-11-06 10:49:19 +01:00
Christian 3fc6982e9a Fix upgrading and downgrading of retained messages QoS for subscriptions (#1564) 2022-11-06 10:35:59 +01:00
SilverFox 5b1cc7d713 Also cleanup MqttClientSessionsManager._subscriberSessions when session is taken over by a new one with CleanSession=true, fix #1552 (#1553) 2022-11-06 10:35:15 +01:00
Christian 2842f27522 1552 mqttnetservermqttclientsessionsmanager subscribersessions is not proper removed on special case (#1562) 2022-11-06 10:34:20 +01:00
Christian 7ef137e26e No longer fire _ClientAcknowledgedPublishPacketEvent_ for PUBREC packets (#1550) (#1551) 2022-11-06 10:29:49 +01:00
Christian b1a336eb62 Reset release notes, update version 2022-11-06 10:24:58 +01:00
Christian d481e93832 Add new samples. 2022-10-25 17:29:45 +02:00
Christian 8cf5537043 Improve unit tests 2022-10-23 08:45:16 +02:00
Christian d2ce9d607a Fix freezes in _AsyncLock_ (#1549) 2022-10-23 08:16:45 +02:00
Christian 36736914bc Fix Xamarin connect freeze (#1545) 2022-10-23 07:51:19 +02:00
Christian 7823d8342e Update version. 2022-10-23 07:43:31 +02:00
Christian 431142471f Add samples. 2022-10-16 11:12:08 +02:00
Christian a3227a444a Reset release notes. 2022-10-01 16:51:11 +02:00
Christian f74af45fba Add new samples. 2022-10-01 11:32:14 +02:00
Christian e050e2d61e Improve async lock (#1520) 2022-09-25 10:26:18 +02:00
Christian ef85ef676a Update README.md 2022-09-16 08:24:52 +02:00
Christian e977546297 Fix cross access to async events. (#1515)
fix cross access to async events.
2022-09-16 08:20:42 +02:00
Rido ae92b0ed33 Add Extensions.MultiCloud to references (#1517) 2022-09-15 09:21:39 +02:00
Christian 7e686d9aa5 Refactor code. 2022-09-15 09:09:08 +02:00
Christian d23bbf7bf3 Improve PingAsync timeout handling. 2022-09-14 14:17:59 +02:00
Christian d4dad4860a Fix and improve session takeover process handling. 2022-09-14 14:15:35 +02:00
Christian c009cbef76 Update comments, release notes, version. 2022-09-13 09:07:02 +02:00
logicaloud 37e60c73ae Send DISCONNECT packets to MQTT5 clients only (#1506) 2022-09-13 09:00:21 +02:00
Christian 3ab095a568 Set web socket boundaries correctly (#1514) 2022-09-13 08:50:02 +02:00
Christian f5d9b65139 Add samples. 2022-08-23 20:58:46 +02:00
Christian 31e5b40459 Revert "Update ci.yml"
This reverts commit 8fb55d75fe.
2022-08-23 20:49:40 +02:00
Christian 8fb55d75fe Update ci.yml 2022-08-14 13:06:32 +02:00
Christian 420222511b Update ReleaseNotes.md 2022-08-13 12:56:05 +02:00
Stan WijckmansandStan Wijckmans c5cec60809 Apply timeout to automatic ping. (#1495)
Co-authored-by: Stan Wijckmans <stanw@aviobook.aero>
2022-08-13 12:53:55 +02:00
Christian 12dd815421 Fix wrong package references in Benchmark project. 2022-08-13 12:45:02 +02:00
Christian f36f49cb3e Add parameter validations. 2022-08-13 12:38:36 +02:00
121 changed files with 3469 additions and 2018 deletions
+5
View File
@@ -0,0 +1,5 @@
* [Core] Fixed several dead locks by implementing a new async signal (#1552)
* [Client] All waiting tasks will now fail when the client connection gets closed. This fixes several freezes (#1561)
* [Server] Fixed duplicated invocation of the event _ClientAcknowledgedPublishPacketAsync_ for QoS level 2 (#1550)
* [Server] Fixed issue in upgrading and downgrading of QoS levels for subscriptions and retained messages (#1560)
* [Server] Fixed memory leak when old sessions are discarded (#1553)
+1 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.1.0.${{github.run_number}}"
VERSION: "4.1.3.${{github.run_number}}"
jobs:
build:
+3
View File
@@ -210,6 +210,7 @@
&lt;/Or&gt;
&lt;/Entry.Match&gt;
&lt;Entry.SortBy&gt;
&lt;Access Order="Public Internal Protected Private" /&gt;
&lt;Name /&gt;
&lt;/Entry.SortBy&gt;
&lt;/Entry&gt;
@@ -240,5 +241,7 @@ 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_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -34
View File
@@ -12,7 +12,7 @@
# MQTTnet
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5.
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture.
## Features
@@ -50,39 +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
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Supported frameworks
| Framwork | Version |
| ------------------ | ----------- |
|.NET | 5.0+ |
|.NET Framework | 4.5.2+ |
|.NET Standard | 1.3+ |
|.NET Core | 1.1+ |
|.NET Core App | 1.1+ |
| Mono | 5.2+ |
| UWP | 10.0.10240+ |
| Xamarin.Android | 7.5+ |
| Xamarin.iOS | 10.14+ |
| Blazor WebAssembly | 3.2.0+ |
## References
This library is used in the following projects:
* Azure Functions MQTT Bindings, <https://github.com/keesschollaart81/CaseOnline.Azure.WebJobs.Extensions.Mqtt>)
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
* MQTT Client Rx (Managed Client Wrapper for Reactive Extensions, <https://github.com/mmuecke/RxMQTTnet>)
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
* MQTTnet App (Cross platform client application for MQTT debugging, inspection etc., <https://github.com/chkr1011/MQTTnetApp>)
* Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
* SparkplugNet (Sparkplug library for .Net, <https://github.com/SeppPenner/SparkplugNet>)
* Silverback (Framework to build event-driven applications - support for MQTT, Kafka & RabbitMQ) <https://github.com/BEagle1984/silverback>
Further projects using this project can be found under https://github.com/dotnet/MQTTnet/network/dependents.
If you use this library and want to see your project here please create a pull request.
## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+56 -15
View File
@@ -10,7 +10,7 @@ using System.Security.Authentication;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
using MQTTnet.Samples.Shared;
using MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Client;
@@ -119,7 +119,7 @@ public static class Client_Connection_Samples
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true;
// The default value is determined by the OS. Set manually to force version.
o.SslProtocol = SslProtocols.Tls12;
})
@@ -134,15 +134,17 @@ public static class Client_Connection_Samples
}
}
public static async Task Connect_Client_Using_WebSockets()
public static async Task Connect_Client_Using_WebSocket4Net()
{
/*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms.
*
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
*/
var mqttFactory = new MqttFactory();
var mqttFactory = new MqttFactory().UseWebSocket4Net();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
@@ -155,18 +157,16 @@ public static class Client_Connection_Samples
response.DumpToConsole();
}
}
public static async Task Connect_Client_Using_WebSocket4Net()
public static async Task Connect_Client_Using_WebSockets()
{
/*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms.
*
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
*/
var mqttFactory = new MqttFactory().UseWebSocket4Net();
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
@@ -206,7 +206,7 @@ public static class Client_Connection_Samples
using (var timeout = new CancellationTokenSource(5000))
{
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected.");
response.DumpToConsole();
@@ -214,6 +214,49 @@ public static class Client_Connection_Samples
}
}
public static async Task Disconnect_Clean()
{
/*
* This sample disconnects from the server with sending a DISCONNECT packet.
* This way of disconnecting is treated as a clean disconnect which will not
* trigger sending the last will etc.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
}
}
public static async Task Disconnect_Non_Clean()
{
/*
* This sample disconnects from the server without sending a DISCONNECT packet.
* This way of disconnecting is treated as a non clean disconnect which will
* trigger sending the last will etc.
*/
var mqttFactory = new MqttFactory();
var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
// without sending a DISCONNECT packet to the server.
mqttClient.Dispose();
}
public static async Task Inspect_Certificate_Validation_Errors()
{
/*
@@ -248,7 +291,7 @@ public static class Client_Connection_Samples
}
}
}
public static async Task Ping_Server()
{
/*
@@ -321,14 +364,12 @@ public static class Client_Connection_Samples
{
try
{
// This code will also do the very first connect! So no call to _ConnectAsync_ is required
// in the first place.
if (!mqttClient.IsConnected)
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
if (!await mqttClient.TryPingAsync())
{
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Subscribe to topics when session is clean etc.
Console.WriteLine("The MQTT client is connected.");
}
}
+47
View File
@@ -40,6 +40,53 @@ public static class Client_Publish_Samples
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
}
public static async Task Publish_Multiple_Application_Messages()
{
/*
* This sample pushes multiple simple application message including a topic and a payload.
*
* See sample _Publish_Application_Message_ 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);
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("20.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("21.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
}
+104 -9
View File
@@ -7,7 +7,9 @@
// ReSharper disable InconsistentNaming
using MQTTnet.Client;
using MQTTnet.Samples.Shared;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Client;
@@ -23,9 +25,7 @@ public static class Client_Subscribe_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
// Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all
@@ -41,7 +41,11 @@ public static class Client_Subscribe_Samples
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/2"); })
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/2");
})
.Build();
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -53,6 +57,95 @@ public static class Client_Subscribe_Samples
}
}
public static async Task Send_Responses()
{
/*
* This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work!
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
{
// Do some work with the message...
// Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!";
// User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
// Now the broker will resend the message again.
return Task.CompletedTask;
};
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Subscribe_Multiple_Topics()
{
/*
* This sample subscribes to several topics in a single request.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Create the subscribe options including several topics with different options.
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/2").WithNoLocal();
})
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/3").WithRetainHandling(MqttRetainHandling.SendAtSubscribe);
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topics.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Subscribe_Topic()
{
/*
@@ -63,14 +156,16 @@ public static class Client_Subscribe_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/1"); })
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
+11 -25
View File
@@ -8,7 +8,6 @@
using System.Text;
using MQTTnet.Diagnostics;
using MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Diagnostics;
@@ -23,7 +22,9 @@ public static class Logger_Samples
var mqttFactory = new MqttFactory(new MyLogger());
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
@@ -31,31 +32,13 @@ public static class Logger_Samples
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
}
public static async Task Log_To_Console()
{
/*
* This sample covers the creation of a custom logger which is used to log to the console.
*/
var mqttFactory = new MqttFactory(new ConsoleLogger());
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected. Press any key to exit.");
Console.ReadKey(true);
}
}
public static async Task Use_Event_Logger()
{
/*
@@ -81,7 +64,9 @@ public static class Logger_Samples
var mqttFactory = new MqttFactory(mqttEventLogger);
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
@@ -89,7 +74,8 @@ public static class Logger_Samples
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
@@ -97,7 +83,7 @@ public static class Logger_Samples
sealed class MyLogger : IMqttNetLogger
{
public bool IsEnabled { get; } = true;
public bool IsEnabled { get; set; } = true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
{
@@ -4,7 +4,7 @@
using System.Text.Json;
namespace MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Helpers;
internal static class ObjectExtensions
{
-1
View File
@@ -8,7 +8,6 @@ Console.WriteLine("Welcome to MQTTnet samples!");
Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
sampleClasses.RemoveAll(c => c.Namespace.Contains(".Shared"));
var index = 0;
foreach (var sampleClass in sampleClasses)
@@ -0,0 +1,47 @@
// 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
// ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local
using MQTTnet.Internal;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
public static class Server_Intercepting_Samples
{
public static async Task Intercept_Application_Messages()
{
/*
* This sample starts a simple MQTT server which manipulate all processed application messages.
* Please see _Server_Simple_Samples_ for more details on how to start a server.
*/
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
mqttServer.InterceptingPublishAsync += args =>
{
// Here we only change the topic of the received application message.
// but also changing the payload etc. is required. Changing the QoS after
// transmitting is not supported and makes no sense at all.
args.ApplicationMessage.Topic += "/manipulated";
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
}
}
+47 -1
View File
@@ -6,8 +6,8 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet.Diagnostics;
using MQTTnet.Protocol;
using MQTTnet.Samples.Shared;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
@@ -170,4 +170,50 @@ public static class Server_Simple_Samples
await server.StartAsync();
return server;
}
class ConsoleLogger : IMqttNetLogger
{
readonly object _consoleSyncRoot = new();
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
{
var foregroundColor = ConsoleColor.White;
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
foregroundColor = ConsoleColor.White;
break;
case MqttNetLogLevel.Info:
foregroundColor = ConsoleColor.Green;
break;
case MqttNetLogLevel.Warning:
foregroundColor = ConsoleColor.DarkYellow;
break;
case MqttNetLogLevel.Error:
foregroundColor = ConsoleColor.Red;
break;
}
if (parameters?.Length > 0)
{
message = string.Format(message, parameters);
}
lock (_consoleSyncRoot)
{
Console.ForegroundColor = foregroundColor;
Console.WriteLine(message);
if (exception != null)
{
Console.WriteLine(exception);
}
}
}
}
}
-53
View File
@@ -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.Diagnostics;
namespace MQTTnet.Samples.Shared;
public sealed class ConsoleLogger : IMqttNetLogger
{
readonly object _consoleSyncRoot = new();
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
{
var foregroundColor = ConsoleColor.White;
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
foregroundColor = ConsoleColor.White;
break;
case MqttNetLogLevel.Info:
foregroundColor = ConsoleColor.Green;
break;
case MqttNetLogLevel.Warning:
foregroundColor = ConsoleColor.DarkYellow;
break;
case MqttNetLogLevel.Error:
foregroundColor = ConsoleColor.Red;
break;
}
if (parameters?.Length > 0)
{
message = string.Format(message, parameters);
}
lock (_consoleSyncRoot)
{
Console.ForegroundColor = foregroundColor;
Console.WriteLine(message);
if (exception != null)
{
Console.WriteLine(exception);
}
}
}
}
@@ -1,26 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class ConnectionContextMockup : ConnectionContext
{
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; }
public ConnectionContextMockup()
{
//Transport = new DefaultConnectionContext
}
}
}
@@ -2,20 +2,24 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Connections;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Server;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class ConnectionHandlerMockup: IMqttServerAdapter
public sealed class ConnectionHandlerMockup : IMqttServerAdapter
{
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
public void Dispose()
{
}
public async Task OnConnectedAsync(ConnectionContext connection)
{
@@ -42,9 +46,5 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
{
return Task.CompletedTask;
}
public void Dispose()
{
}
}
}
}
@@ -6,7 +6,7 @@ using System.IO.Pipelines;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class DuplexPipeMockup : IDuplexPipe
public sealed class DuplexPipeMockup : IDuplexPipe
{
public DuplexPipeMockup()
{
@@ -20,9 +20,8 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
PipeWriter IDuplexPipe.Output => Send.Writer;
public Pipe Receive { get; }
public Pipe Receive { get; set; }
public Pipe Send { get; set; }
public Pipe Send { get; }
}
}
@@ -6,7 +6,7 @@ using System.Buffers;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class LimitedMemoryPool : MemoryPool<byte>
public sealed class LimitedMemoryPool : MemoryPool<byte>
{
protected override void Dispose(bool disposing)
{
@@ -7,9 +7,9 @@ using System.Buffers;
namespace MQTTnet.AspNetCore.Tests.Mockups
{
public class MemoryOwner : IMemoryOwner<byte>
public sealed class MemoryOwner : IMemoryOwner<byte>
{
private readonly byte[] _raw;
readonly byte[] _raw;
public MemoryOwner(int size)
{
@@ -27,11 +27,11 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
Memory = _raw;
}
public Memory<byte> Memory { get; }
public void Dispose()
{
ArrayPool<byte>.Shared.Return(_raw);
}
public Memory<byte> Memory { get; }
}
}
@@ -2,22 +2,21 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Tests.Mockups;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Client;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
@@ -26,20 +25,6 @@ namespace MQTTnet.AspNetCore.Tests
[TestClass]
public class MqttConnectionContextTest
{
[TestMethod]
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
{
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var pipe = new DuplexPipeMockup();
var connection = new DefaultConnectionContext();
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
pipe.Receive.Writer.Complete();
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
}
[TestMethod]
public async Task TestCorruptedConnectPacket()
{
@@ -49,13 +34,43 @@ namespace MQTTnet.AspNetCore.Tests
var connection = new DefaultConnectionContext();
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
}
[TestMethod]
public async Task TestEndpoint()
{
var mockup = new ConnectionHandlerMockup();
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
.UseStartup<Startup>()
.ConfigureServices(
(hostContext, services) =>
{
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
services.AddSingleton<IMqttServerAdapter>(mockup);
})
.Build())
using (var client = new MqttFactory().CreateMqttClient())
{
host.Start();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None);
var ctx = await mockup.Context.Task;
#if NETCOREAPP3_1
var ep = IPEndPoint.Parse(ctx.Endpoint);
Assert.IsNotNull(ep);
#endif
Assert.IsNotNull(ctx);
}
}
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
@@ -78,7 +93,7 @@ namespace MQTTnet.AspNetCore.Tests
// await Task.WhenAll(tasks).ConfigureAwait(false);
//}
[TestMethod]
public async Task TestLargePacket()
{
@@ -94,43 +109,25 @@ namespace MQTTnet.AspNetCore.Tests
Assert.IsTrue(readResult.Buffer.Length > 20000);
}
private class Startup
[TestMethod]
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
{
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var pipe = new DuplexPipeMockup();
var connection = new DefaultConnectionContext();
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
pipe.Receive.Writer.Complete();
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
}
class Startup
{
public void Configure(IApplicationBuilder app)
{
{
}
}
[TestMethod]
public async Task TestEndpoint()
{
var mockup = new ConnectionHandlerMockup();
using (var host = new WebHostBuilder()
.UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
.UseStartup<Startup>()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
services.AddSingleton<IMqttServerAdapter>(mockup);
})
.Build())
using (var client = new MqttFactory().CreateMqttClient())
{
host.Start();
await client.ConnectAsync(new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build(), CancellationToken.None);
var ctx = await mockup.Context.Task;
#if NETCOREAPP3_1
var ep = IPEndPoint.Parse(ctx.Endpoint);
Assert.IsNotNull(ep);
#endif
Assert.IsNotNull(ctx);
}
}
}
}
}
@@ -12,7 +12,7 @@ using MQTTnet.Packets;
namespace MQTTnet.AspNetCore.Tests
{
[TestClass]
public class ReaderExtensionsTest
public sealed class ReaderExtensionsTest
{
[TestMethod]
public void TestTryDeserialize()
@@ -171,7 +171,7 @@ namespace MQTTnet.AspNetCore
{
var formatter = PacketFormatterAdapter;
using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false))
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory();
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Internal;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class AsyncLockBenchmark : BaseBenchmark
{
[Benchmark]
public async Task Synchronize_100_Tasks()
{
const int tasksCount = 100;
var tasks = new Task[tasksCount];
var asyncLock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{
tasks[i] = Task.Run(
async () =>
{
using (await asyncLock.EnterAsync().ConfigureAwait(false))
{
var localI = globalI;
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
}
await Task.WhenAll(tasks).ConfigureAwait(false);
if (globalI != tasksCount)
{
throw new Exception($"Code is broken ({globalI})!");
}
}
[Benchmark]
public async Task Wait_100_000_Times()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource())
{
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
}
}
}
}
@@ -12,14 +12,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes;
using MQTTnet.Channel;
using MQTTnet.Implementations;
@@ -70,8 +71,8 @@ namespace MQTTnet.Benchmarks
while (read < expected)
{
var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readresult;
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readResult;
}
}
@@ -79,9 +80,11 @@ namespace MQTTnet.Benchmarks
{
await Task.Yield();
var buffer = new ArraySegment<byte>(new byte[size]);
for (var i = 0; i < iterations; i++)
{
await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
}
}
}
+4
View File
@@ -29,6 +29,7 @@ namespace MQTTnet.Benchmarks
Console.WriteLine("c = SubscribeBenchmark");
Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark");
Console.WriteLine("f = AsyncLockBenchmark");
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
@@ -75,6 +76,9 @@ namespace MQTTnet.Benchmarks
case 'e':
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
break;
case 'f':
BenchmarkRunner.Run<AsyncLockBenchmark>();
break;
}
Console.ReadLine();
@@ -104,7 +104,7 @@ namespace MQTTnet.Benchmarks
return Task.FromResult(count);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
@@ -167,7 +167,7 @@ namespace MQTTnet.Extensions.ManagedClient
try
{
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false))
{
if (_messageQueue.Count >= Options.MaxPendingMessages)
{
@@ -296,7 +296,7 @@ namespace MQTTnet.Extensions.ManagedClient
_subscriptionsQueuedSignal.Release();
return Task.FromResult(0);
return CompletedTask.Instance;
}
public Task UnsubscribeAsync(ICollection<string> topics)
@@ -319,7 +319,7 @@ namespace MQTTnet.Extensions.ManagedClient
_subscriptionsQueuedSignal.Release();
return Task.FromResult(0);
return CompletedTask.Instance;
}
protected override void Dispose(bool disposing)
@@ -693,7 +693,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{
// While publishing this message, this.PublishAsync could have booted this
// message off the queue to make room for another (when using a cap
@@ -724,7 +724,7 @@ namespace MQTTnet.Extensions.ManagedClient
//contradict the expected behavior of QoS 1 and 2, that's also true
//for the usage of a message queue cap, so it's still consistent
//with prior behavior in that way.
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{
_messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
@@ -34,7 +34,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await _messagesLock.EnterAsync().ConfigureAwait(false))
{
_messages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false);
@@ -45,7 +45,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await _messagesLock.EnterAsync().ConfigureAwait(false))
{
var index = _messages.IndexOf(applicationMessage);
if (index == -1)
@@ -9,7 +9,7 @@ using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Extensions.Rpc
{
@@ -116,7 +116,7 @@ namespace MQTTnet.Extensions.Rpc
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
#if NET452
@@ -128,7 +128,7 @@ namespace MQTTnet.Extensions.Rpc
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public void Dispose()
@@ -14,6 +14,7 @@ using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using SuperSocket.ClientEngine;
using WebSocket4Net;
@@ -124,7 +125,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocket.Close();
}
return Task.FromResult(0);
return CompletedTask.Instance;
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -154,10 +155,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return Task.FromResult(readBytes);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
_webSocket.Send(buffer, offset, count);
return Task.FromResult(0);
_webSocket.Send(buffer.Array, buffer.Offset, buffer.Count);
return CompletedTask.Instance;
}
public void Dispose()
+28
View File
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.TestApp
{
public sealed class AsyncLockTest
{
public async Task Run()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource())
{
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
}
}
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ using System;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.TestApp
@@ -40,7 +40,7 @@ namespace MQTTnet.TestApp
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
client.ConnectedAsync += async e =>
+3 -3
View File
@@ -9,7 +9,7 @@ using Newtonsoft.Json;
using System.Collections.Generic;
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.TestApp
@@ -42,7 +42,7 @@ namespace MQTTnet.TestApp
managedClient.ApplicationMessageReceivedAsync += e =>
{
Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await managedClient.StartAsync(options);
@@ -85,7 +85,7 @@ namespace MQTTnet.TestApp
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{
File.WriteAllText(Filename, JsonConvert.SerializeObject(messages));
return Task.FromResult(0);
return CompletedTask.Instance;
}
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
@@ -5,6 +5,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using MQTTnet.Internal;
namespace MQTTnet.TestApp
{
@@ -124,7 +125,7 @@ namespace MQTTnet.TestApp
{
_cancellationTokenSource.Cancel();
}
return Implementations.PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public async Task Cleanup()
+5
View File
@@ -28,6 +28,7 @@ namespace MQTTnet.TestApp
Console.WriteLine("c = Start QoS 0 benchmark");
Console.WriteLine("d = Start server with logging");
Console.WriteLine("e = Start Message Throughput Test");
Console.WriteLine("f = Start AsyncLock Test");
var pressedKey = Console.ReadKey(true);
if (pressedKey.KeyChar == '1')
@@ -88,6 +89,10 @@ namespace MQTTnet.TestApp
{
Task.Run(new MessageThroughputTest().Run);
}
else if (pressedKey.KeyChar == 'f')
{
Task.Run(new AsyncLockTest().Run);
}
Thread.Sleep(Timeout.Infinite);
}
+2 -2
View File
@@ -8,7 +8,7 @@ using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.TestApp
@@ -144,7 +144,7 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.ConnectAsync(options);
+9 -11
View File
@@ -4,13 +4,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
using Newtonsoft.Json;
@@ -69,13 +67,13 @@ namespace MQTTnet.TestApp
}
File.WriteAllText(Filename, JsonConvert.SerializeObject(e.StoredRetainedMessages));
return Task.FromResult(0);
return CompletedTask.Instance;
};
mqttServer.RetainedMessagesClearedAsync += e =>
{
File.Delete(Filename);
return Task.FromResult(0);
return CompletedTask.Instance;
};
mqttServer.LoadingRetainedMessageAsync += e =>
@@ -93,7 +91,7 @@ namespace MQTTnet.TestApp
e.LoadedRetainedMessages = retainedMessages;
return Task.FromResult(0);
return CompletedTask.Instance;
};
mqttServer.InterceptingPublishAsync += e =>
@@ -111,7 +109,7 @@ namespace MQTTnet.TestApp
e.CloseConnection = true;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
mqttServer.ValidatingConnectionAsync += e =>
@@ -124,7 +122,7 @@ namespace MQTTnet.TestApp
}
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
mqttServer.InterceptingSubscriptionAsync += e =>
@@ -140,7 +138,7 @@ namespace MQTTnet.TestApp
e.CloseConnection = true;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
mqttServer.InterceptingPublishAsync += e =>
@@ -149,7 +147,7 @@ namespace MQTTnet.TestApp
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
ConsoleColor.Magenta);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
//options.ApplicationMessageInterceptor = c =>
@@ -177,7 +175,7 @@ namespace MQTTnet.TestApp
mqttServer.ClientConnectedAsync += e =>
{
Console.Write("Client disconnected event fired.");
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
@@ -11,18 +11,62 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Tests.Client
{
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
{
[TestMethod]
public async Task Subscribe_When_Offline_But_Existing_Session()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer(o => o.WithPersistentSessions());
// The connected client will crate a new session.
var mqttClient = await testEnvironment.ConnectClient();
mqttClient.Dispose();
mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
// Subscribe before starting!
await managedClient.SubscribeAsync("a");
MqttApplicationMessage applicationMessage = null;
managedClient.ApplicationMessageReceivedAsync += e =>
{
applicationMessage = e.ApplicationMessage;
return CompletedTask.Instance;
};
// Now the client must send the new subscription.
await managedClient.StartAsync(
new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithCleanSession(false).WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()
});
await LongTestDelay();
await server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "a"
}));
await LongTestDelay();
Assert.IsNotNull(applicationMessage);
}
}
[TestMethod]
public async Task Connect_To_Invalid_Server()
{
@@ -31,7 +75,6 @@ namespace MQTTnet.Tests.Client
testEnvironment.IgnoreClientLogErrors = true;
var mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
ConnectingFailedEventArgs connectingFailedEventArgs = null;
@@ -39,7 +82,7 @@ namespace MQTTnet.Tests.Client
managedClient.ConnectingFailedAsync += args =>
{
connectingFailedEventArgs = args;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await managedClient.StartAsync(
@@ -117,7 +160,7 @@ namespace MQTTnet.Tests.Client
receivingClient.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await receivingClient.SubscribeAsync("My/last/will");
@@ -281,7 +324,7 @@ namespace MQTTnet.Tests.Client
});
// Wait a bit for the retained message to be available
await Task.Delay(1000);
await LongTestDelay();
await sendingClient.DisconnectAsync();
@@ -290,28 +333,29 @@ namespace MQTTnet.Tests.Client
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
managedClient.ApplicationMessageReceivedAsync += e =>
{
receivedManagedMessages.Add(e.ApplicationMessage);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await Task.Delay(1000);
await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync();
await Task.Delay(500);
await LongTestDelay();
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await Task.Delay(1000);
await LongTestDelay();
// After reconnect and then some delay, the retained message must not be received,
// showing that the subscriptions were cleared
@@ -319,7 +363,9 @@ namespace MQTTnet.Tests.Client
// Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic");
await Task.Delay(500);
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count);
}
}
@@ -428,7 +474,7 @@ namespace MQTTnet.Tests.Client
managedClient.ConnectedAsync += e =>
{
connected.TrySetResult(true);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
return connected.Task;
@@ -457,7 +503,7 @@ namespace MQTTnet.Tests.Client
result.TrySetResult(receivedMessages);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
return result.Task;
@@ -486,7 +532,7 @@ namespace MQTTnet.Tests.Client
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{
_messages = messages;
return Task.FromResult(0);
return CompletedTask.Instance;
}
}
}
+41 -36
View File
@@ -14,15 +14,17 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client
{
[TestClass]
public sealed class Client_Tests : BaseTestClass
public sealed class MqttClient_Tests : BaseTestClass
{
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
@@ -56,7 +58,7 @@ namespace MQTTnet.Tests.Client
}
_ = InvokeInternal();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos));
@@ -107,7 +109,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
@@ -121,7 +123,7 @@ namespace MQTTnet.Tests.Client
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
}
await Task.Delay(100); // disconnected handler is called async
await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
}
}
@@ -136,7 +138,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e =>
{
ex = e.Exception;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
try
@@ -145,6 +147,7 @@ namespace MQTTnet.Tests.Client
}
catch
{
// Ignore errors.
}
await Task.Delay(500);
@@ -167,7 +170,7 @@ namespace MQTTnet.Tests.Client
server.InterceptingPublishAsync += c =>
{
i++;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.SendAsync(
@@ -208,7 +211,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e =>
{
handlerFired = true;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await server.StopAsync();
@@ -239,10 +242,10 @@ namespace MQTTnet.Tests.Client
for (var i = 0; i < 98; i++)
{
Assert.IsFalse(clients[i].IsConnected, $"clients[{i}] is not connected");
Assert.IsFalse(clients[i].TryPingAsync().GetAwaiter().GetResult(), $"clients[{i}] is not connected");
}
Assert.IsTrue(clients[99].IsConnected);
Assert.IsTrue(clients[99].TryPingAsync().GetAwaiter().GetResult());
Assert.AreEqual(1, clientStatus.Count);
Assert.AreEqual(1, sessionStatus.Count);
@@ -252,7 +255,7 @@ namespace MQTTnet.Tests.Client
receiveClient.ApplicationMessageReceivedAsync += e =>
{
receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await receiveClient.SubscribeAsync("x");
@@ -310,7 +313,7 @@ namespace MQTTnet.Tests.Client
receiver.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await sender.PublishAsync(message.Build(), CancellationToken.None);
@@ -332,7 +335,7 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
}
}
@@ -343,11 +346,11 @@ namespace MQTTnet.Tests.Client
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
await client.DisconnectAsync();
Assert.IsFalse(client.IsConnected);
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult());
}
}
@@ -453,7 +456,7 @@ namespace MQTTnet.Tests.Client
receivedValues.Add(value);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
client1.ApplicationMessageReceivedAsync += Handler1;
@@ -531,7 +534,7 @@ namespace MQTTnet.Tests.Client
client2.ApplicationMessageReceivedAsync += e =>
{
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client2.SubscribeAsync(client2Topic);
@@ -566,7 +569,7 @@ namespace MQTTnet.Tests.Client
receivedMessages.Add(e.ApplicationMessage);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client1.SubscribeAsync("a");
@@ -591,17 +594,17 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient();
await Task.Delay(500);
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(await client.TryPingAsync());
await server.StopAsync();
await Task.Delay(500);
Assert.IsFalse(client.IsConnected);
Assert.IsFalse(await client.TryPingAsync());
await server.StartAsync();
await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(await client.TryPingAsync());
}
}
@@ -637,6 +640,7 @@ namespace MQTTnet.Tests.Client
}
catch
{
// Ignore errors.
}
SpinWait.SpinUntil(() => tries >= maxTries, 10000);
@@ -656,11 +660,11 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient();
await Task.Delay(500);
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
await server.StopAsync();
await Task.Delay(500);
Assert.IsFalse(client.IsConnected);
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult());
for (var i = 0; i < 5; i++)
{
@@ -671,6 +675,7 @@ namespace MQTTnet.Tests.Client
}
catch
{
// Ignore errors.
}
}
@@ -678,7 +683,7 @@ namespace MQTTnet.Tests.Client
await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(client.IsConnected);
await client.PingAsync();
}
}
@@ -723,7 +728,7 @@ namespace MQTTnet.Tests.Client
replies.Add(eventArgs.ApplicationMessage.Topic);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
client2.ApplicationMessageReceivedAsync += Handler2;
@@ -761,7 +766,7 @@ namespace MQTTnet.Tests.Client
replyReceived = true;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
client2.ApplicationMessageReceivedAsync += async e =>
@@ -812,7 +817,7 @@ namespace MQTTnet.Tests.Client
replyReceived = true;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce);
@@ -828,14 +833,14 @@ namespace MQTTnet.Tests.Client
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
client.DisconnectedAsync += e =>
{
Assert.IsTrue(e.ClientWasConnected);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.DisconnectAsync();
@@ -851,11 +856,11 @@ namespace MQTTnet.Tests.Client
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
client.DisconnectedAsync += e =>
{
Assert.IsTrue(e.ClientWasConnected);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await server.StopAsync();
@@ -892,7 +897,7 @@ namespace MQTTnet.Tests.Client
receivedMessages.Add(e.ApplicationMessage);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build());
@@ -917,14 +922,14 @@ namespace MQTTnet.Tests.Client
client1.DisconnectedAsync += c =>
{
disconnectedFired = true;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var messageReceived = false;
client1.ApplicationMessageReceivedAsync += e =>
{
messageReceived = true;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build());
@@ -941,7 +946,7 @@ namespace MQTTnet.Tests.Client
await Task.Delay(500);
Assert.IsTrue(messageReceived);
Assert.IsTrue(client1.IsConnected);
Assert.IsTrue(client1.TryPingAsync().GetAwaiter().GetResult());
Assert.IsFalse(disconnectedFired);
}
}
@@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Diagnostics
{
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Diagnostics
mqttClient.InspectPackage += eventArgs =>
{
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Server;
namespace MQTTnet.Tests.Factory
@@ -56,7 +56,7 @@ namespace MQTTnet.Tests.Factory
managedClient.ConnectingFailedAsync += e =>
{
tcs.TrySetResult(null);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await Task.WhenAny(Task.Delay(managedClient.Options.ClientOptions.Timeout), tcs.Task);
@@ -13,13 +13,12 @@ using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests
namespace MQTTnet.Tests.Formatter
{
[TestClass]
public sealed class MqttPacketSerialization_V3_Binary_Tests
@@ -10,7 +10,7 @@ using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests
namespace MQTTnet.Tests.Formatter
{
[TestClass]
public sealed class MqttPacketSerialization_V3_Tests
@@ -9,7 +9,7 @@ using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests
namespace MQTTnet.Tests.Formatter
{
[TestClass]
public sealed class MqttPacketSerialization_V5_Tests
@@ -6,7 +6,6 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal
@@ -16,6 +15,28 @@ namespace MQTTnet.Tests.Internal
{
int _testEventAsyncCount;
[TestMethod]
public async Task Attach_and_Detach_Sync_And_Async_EventHandler()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEvent += OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(2, _testEventAsyncCount);
testClass.TestEvent -= OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(3, _testEventAsyncCount);
testClass.TestEventAsync -= OnTestEventAsync;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(3, _testEventAsyncCount);
}
[TestMethod]
public async Task Attach_EventHandler()
{
@@ -28,6 +49,19 @@ namespace MQTTnet.Tests.Internal
Assert.AreEqual(1, _testEventAsyncCount);
}
[TestMethod]
public async Task Attach_Sync_And_Async_EventHandler()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEvent += OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(2, _testEventAsyncCount);
}
[TestMethod]
public async Task Detach_EventHandler()
{
@@ -41,22 +75,62 @@ namespace MQTTnet.Tests.Internal
Assert.AreEqual(0, _testEventAsyncCount);
}
[TestMethod]
public void Has_Handlers()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
Assert.AreEqual(true, testClass.HasTestHandlers);
}
[TestMethod]
public void No_Handlers()
{
var testClass = new TestClass();
Assert.AreEqual(false, testClass.HasTestHandlers);
}
[TestMethod]
public void Remove_Handlers()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEventAsync -= OnTestEventAsync;
Assert.AreEqual(false, testClass.HasTestHandlers);
}
void OnTestEvent(EventArgs arg)
{
Interlocked.Increment(ref _testEventAsyncCount);
}
Task OnTestEventAsync(EventArgs arg)
{
Interlocked.Increment(ref _testEventAsyncCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
sealed class TestClass
{
readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>();
public event Action<EventArgs> TestEvent
{
add => _testEvent.AddHandler(value);
remove => _testEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> TestEventAsync
{
add => _testEvent.AddHandler(value);
remove => _testEvent.RemoveHandler(value);
}
public bool HasTestHandlers => _testEvent.HasHandlers;
public Task FireEventAsync(EventArgs eventArgs)
{
return _testEvent.InvokeAsync(eventArgs);
+239 -50
View File
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -11,8 +12,186 @@ using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal
{
[TestClass]
public class AsyncLock_Tests
public sealed class AsyncLock_Tests
{
[TestMethod]
public async Task Cancellation_Of_Awaiter()
{
var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
var counter = 0;
Debug.WriteLine("Prepared locked lock.");
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 1 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 2 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 3 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 4 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 5 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(6)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 6 incremented");
counter++;
}
}
});
Debug.WriteLine("Delay before release...");
await Task.Delay(TimeSpan.FromSeconds(3.1));
releaser.Dispose();
Debug.WriteLine("Wait for all tasks...");
await Task.Delay(TimeSpan.FromSeconds(6.1));
Assert.AreEqual(3, counter);
}
[TestMethod]
public void Lock_Parallel_Tasks()
{
const int taskCount = 50;
var @lock = new AsyncLock();
var tasks = new Task[taskCount];
var globalI = 0;
for (var i = 0; i < taskCount; i++)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
tasks[i] = Task.Run(
async () =>
{
using (await @lock.EnterAsync())
{
var localI = globalI;
await Task.Delay(5); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Task.WaitAll(tasks);
Assert.AreEqual(taskCount, globalI);
}
[TestMethod]
public void Lock_10_Parallel_Tasks_With_Dispose_Doesnt_Lockup()
{
const int ThreadsCount = 10;
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < ThreadsCount; i++)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(
async () =>
{
using (await @lock.EnterAsync())
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
})
.ContinueWith(
x =>
{
if (globalI == 5)
{
@lock.Dispose();
@lock = new AsyncLock();
}
if (x.Exception != null)
{
Debug.WriteLine(x.Exception.GetBaseException().GetType().Name);
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Task.WaitAll(threads);
// Expect only 6 because the others are failing due to disposal (if (globalI == 5)).
Assert.AreEqual(6, globalI);
}
[TestMethod]
public async Task Lock_Serial_Calls()
{
@@ -21,7 +200,7 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock();
for (var i = 0; i < 100; i++)
{
using (await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
using (await @lock.EnterAsync().ConfigureAwait(false))
{
sum++;
}
@@ -37,66 +216,76 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock();
// This call will never "release" the lock due to missing _using_.
await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
await @lock.EnterAsync().ConfigureAwait(false);
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
await @lock.WaitAsync(cts.Token).ConfigureAwait(false);
await @lock.EnterAsync(cts.Token).ConfigureAwait(false);
}
}
//[TestMethod]
//public async Task Test_Cancellation_With_Later_Access()
//{
// var @lock = new AsyncLock();
// var releaser = await @lock.WaitAsync().ConfigureAwait(false);
// try
// {
// using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
// {
// await @lock.WaitAsync(cts.Token).ConfigureAwait(false);
// }
// }
// catch (OperationCanceledException)
// {
// }
// releaser.Dispose();
// using (await @lock.WaitAsync().ConfigureAwait(false))
// {
// // When the method finished, the thread got access.
// }
//}
[TestMethod]
public void Lock_10_Parallel_Tasks()
public async Task Test_Cancellation_With_Later_Access()
{
const int ThreadsCount = 10;
var asyncLock = new AsyncLock();
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < ThreadsCount; i++)
var releaser = await asyncLock.EnterAsync().ConfigureAwait(false);
try
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(async () =>
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
using (var releaser = await @lock.WaitAsync(CancellationToken.None))
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
await asyncLock.EnterAsync(timeout.Token).ConfigureAwait(false);
}
Assert.Fail("Exception should be thrown!");
}
catch (OperationCanceledException)
{
}
Task.WaitAll(threads);
Assert.AreEqual(ThreadsCount, globalI);
releaser.Dispose();
using (await asyncLock.EnterAsync(CancellationToken.None).ConfigureAwait(false))
{
// When the method finished, the thread got access.
}
}
[TestMethod]
public async Task Use_After_Cancellation()
{
var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
await @lock.EnterAsync(cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Expected exception!
}
releaser.Dispose();
// Regular usage after cancellation.
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
}
}
}
}
@@ -0,0 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal
{
[TestClass]
public sealed class AsyncSignal_Tests
{
[TestMethod]
[ExpectedException(typeof(TaskCanceledException))]
public async Task Cancel_If_No_Signal()
{
var asyncSignal = new AsyncSignal();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
await asyncSignal.WaitAsync(timeout.Token);
Assert.Fail("There is no signal. So we must fail here!");
}
}
[TestMethod]
[ExpectedException(typeof(ObjectDisposedException))]
public async Task Dispose_Properly()
{
var asyncSignal = new AsyncSignal();
// The timeout will not be reached but another task will kill the async signal.
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(999)))
{
_ = Task.Run(
async () =>
{
await Task.Delay(2000, CancellationToken.None);
asyncSignal.Dispose();
},
CancellationToken.None);
await asyncSignal.WaitAsync(timeout.Token);
Assert.Fail("There is no signal. So we must fail here!");
}
}
[TestMethod]
public async Task Loop_Signal()
{
var asyncSignal = new AsyncSignal();
for (var i = 0; i < 10; i++)
{
asyncSignal.Set();
// WaitAsync should return directly because the signal is available.
await asyncSignal.WaitAsync();
}
}
[TestMethod]
public async Task Signal()
{
var asyncSignal = new AsyncSignal();
// The timeout will not be reached but another task will kill the async signal.
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
var stopwatch = Stopwatch.StartNew();
_ = Task.Run(
async () =>
{
await Task.Delay(1000, CancellationToken.None);
asyncSignal.Set();
},
CancellationToken.None);
await asyncSignal.WaitAsync(timeout.Token);
stopwatch.Stop();
Assert.IsTrue(stopwatch.ElapsedMilliseconds > 900);
}
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task Fail_For_Two_Waiters()
{
var asyncSignal = new AsyncSignal();
// This thread will wait properly because it is the first waiter.
_ = Task.Run(
async () =>
{
await asyncSignal.WaitAsync();
},
CancellationToken.None);
await Task.Delay(1000);
// Now the current thread must fail because there is already a waiter.
await asyncSignal.WaitAsync();
}
}
}
@@ -15,7 +15,7 @@ namespace MQTTnet.Tests.Internal
public sealed class MqttPacketBus_Tests
{
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
[ExpectedException(typeof(TaskCanceledException))]
public async Task Wait_With_Empty_Bus()
{
var bus = new MqttPacketBus();
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;net461;netcoreapp3.1;net6.0</TargetFrameworks>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
+3 -4
View File
@@ -8,10 +8,9 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.MQTTv5
{
@@ -43,7 +42,7 @@ namespace MQTTnet.Tests.MQTTv5
client.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.PublishAsync(new MqttApplicationMessageBuilder()
@@ -245,7 +244,7 @@ namespace MQTTnet.Tests.MQTTv5
receiver.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+46 -4
View File
@@ -8,7 +8,7 @@ using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.MQTTv5
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests.MQTTv5
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -171,11 +171,53 @@ namespace MQTTnet.Tests.MQTTv5
// Session should be present
Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not");
}
}
MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
[TestMethod]
public async Task Disconnect_with_Reason()
{
using (var testEnvironment = CreateTestEnvironment())
{
var disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
string testClientId = null;
await testEnvironment.StartServer();
testEnvironment.Server.ClientConnectedAsync += e =>
{
testClientId = e.ClientId;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
client.DisconnectedAsync += e =>
{
disconnectReason = e.Reason;
return CompletedTask.Instance;
};
await LongTestDelay();
// Test client should be connected now
Assert.IsTrue(testClientId != null);
// Have the server disconnect the client with AdministrativeAction reason
await testEnvironment.Server.DisconnectClientAsync(testClientId, MqttDisconnectReasonCode.AdministrativeAction);
await LongTestDelay();
// The reason should be returned to the client in the DISCONNECT packet
Assert.AreEqual(MqttClientDisconnectReason.AdministrativeAction, disconnectReason);
}
}
static MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
{
return testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
@@ -2,11 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Mockups
{
@@ -32,12 +34,12 @@ namespace MQTTnet.Tests.Mockups
public Task ConnectAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
return CompletedTask.Instance;
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
return CompletedTask.Instance;
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -45,9 +47,9 @@ namespace MQTTnet.Tests.Mockups
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
return _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken);
}
public void Dispose()
@@ -7,7 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Mockups
{
@@ -46,7 +46,7 @@ namespace MQTTnet.Tests.Mockups
_receivedEventArgs.Add(eventArgs);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
}
}
@@ -13,7 +13,7 @@ using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.Rpc;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.LowLevelClient;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -220,7 +220,7 @@ namespace MQTTnet.Tests.Mockups
}
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
return client;
@@ -261,7 +261,7 @@ namespace MQTTnet.Tests.Mockups
}
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
return Server;
+2 -1
View File
@@ -13,6 +13,7 @@ using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests
{
@@ -189,7 +190,7 @@ namespace MQTTnet.Tests
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await PlatformAbstractionLayer.CompletedTask;
await CompletedTask.Instance;
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+2 -2
View File
@@ -8,7 +8,7 @@ using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests
receiverClient.ApplicationMessageReceivedAsync += e =>
{
response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString());
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await receiverClient.SubscribeAsync("#");
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
@@ -47,20 +47,20 @@ namespace MQTTnet.Tests.Server
e.ReasonCode = MqttConnectReasonCode.Success;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
testEnvironment.Server.ClientConnectedAsync += args =>
{
serverConnectedClientId = args.ClientId;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
testEnvironment.Server.ClientDisconnectedAsync += args =>
{
serverDisconnectedClientId = args.ClientId;
disconnectedMre.Set();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
// Arrange client
@@ -68,7 +68,7 @@ namespace MQTTnet.Tests.Server
client.ConnectedAsync += args =>
{
clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
// Act
+8 -8
View File
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -27,7 +27,7 @@ namespace MQTTnet.Tests.Server
server.ClientConnectedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
@@ -54,7 +54,7 @@ namespace MQTTnet.Tests.Server
server.ClientDisconnectedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
@@ -81,7 +81,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -108,7 +108,7 @@ namespace MQTTnet.Tests.Server
server.ClientUnsubscribedTopicAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -134,7 +134,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -161,7 +161,7 @@ namespace MQTTnet.Tests.Server
server.StartedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await server.StartAsync();
@@ -183,7 +183,7 @@ namespace MQTTnet.Tests.Server
server.StoppedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await server.StopAsync();
+85 -31
View File
@@ -11,8 +11,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -99,7 +98,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e =>
{
e.ReasonCode = MqttConnectReasonCode.NotAuthorized;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var connectingFailedException = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => testEnvironment.ConnectClient());
@@ -122,7 +121,7 @@ namespace MQTTnet.Tests.Server
e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
// Prepare some retained messages.
@@ -146,7 +145,7 @@ namespace MQTTnet.Tests.Server
buffer.Append(e.ApplicationMessage.Topic);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client2.SubscribeAsync("y");
@@ -172,13 +171,13 @@ namespace MQTTnet.Tests.Server
server.ClientConnectedAsync += e =>
{
Interlocked.Increment(ref clientConnectedCalled);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
server.ClientDisconnectedAsync += e =>
{
Interlocked.Increment(ref clientDisconnectedCalled);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
@@ -225,7 +224,7 @@ namespace MQTTnet.Tests.Server
// Clear retained message.
await client.PublishAsync(
new MqttApplicationMessageBuilder().WithTopic("r" + i2)
.WithPayload(PlatformAbstractionLayer.EmptyByteArray)
.WithPayload(EmptyBuffer.Array)
.WithRetainFlag()
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build());
@@ -276,7 +275,7 @@ namespace MQTTnet.Tests.Server
{
e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" };
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
string receivedTopic = null;
@@ -286,7 +285,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += a =>
{
receivedTopic = a.ApplicationMessage.Topic;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build());
@@ -305,7 +304,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var c1 = await testEnvironment.ConnectClient();
@@ -316,7 +315,7 @@ namespace MQTTnet.Tests.Server
c2.ApplicationMessageReceivedAsync += e =>
{
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build());
@@ -339,7 +338,7 @@ namespace MQTTnet.Tests.Server
server.ApplicationMessageNotConsumedAsync += e =>
{
undeliverd = e.ApplicationMessage.Topic;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -376,7 +375,7 @@ namespace MQTTnet.Tests.Server
receivedMessages.Add(e.ApplicationMessage);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await Task.Delay(500);
@@ -400,7 +399,7 @@ namespace MQTTnet.Tests.Server
s.RetainedMessageChangedAsync += e =>
{
savedRetainedMessages = e.StoredRetainedMessages;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var c1 = await testEnvironment.ConnectClient();
@@ -440,7 +439,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
receivedTopic = e.ApplicationMessage.Topic;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync("#");
@@ -487,7 +486,7 @@ namespace MQTTnet.Tests.Server
client.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build();
@@ -521,13 +520,13 @@ namespace MQTTnet.Tests.Server
c2.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
c3.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false);
@@ -581,7 +580,7 @@ namespace MQTTnet.Tests.Server
events.Add("c");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
server.ClientDisconnectedAsync += e =>
@@ -591,7 +590,7 @@ namespace MQTTnet.Tests.Server
events.Add("d");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString());
@@ -621,7 +620,7 @@ namespace MQTTnet.Tests.Server
events.Add("r");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c2.SubscribeAsync("topic");
@@ -669,7 +668,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e =>
{
ConnectionValidationHandler(e);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var events = new List<string>();
@@ -681,7 +680,7 @@ namespace MQTTnet.Tests.Server
events.Add("c");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
server.ClientDisconnectedAsync += e =>
@@ -691,7 +690,7 @@ namespace MQTTnet.Tests.Server
events.Add("d");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id");
@@ -706,7 +705,7 @@ namespace MQTTnet.Tests.Server
events.Add("x");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
c1.ApplicationMessageReceivedAsync += e =>
@@ -716,7 +715,7 @@ namespace MQTTnet.Tests.Server
events.Add("r");
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
c1.SubscribeAsync("topic").Wait();
@@ -785,7 +784,7 @@ namespace MQTTnet.Tests.Server
client1.ApplicationMessageReceivedAsync += e =>
{
receivedBody = e.ApplicationMessage.Payload;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client1.SubscribeAsync("string");
@@ -822,7 +821,7 @@ namespace MQTTnet.Tests.Server
receivedMessages.Add(e.ApplicationMessage);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await Task.Delay(500);
@@ -850,7 +849,7 @@ namespace MQTTnet.Tests.Server
c1.DisconnectedAsync += e =>
{
disconnectCalled++;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await Task.Delay(100);
@@ -935,7 +934,62 @@ namespace MQTTnet.Tests.Server
Assert.IsTrue(client.IsConnected);
}
}
[TestMethod]
public async Task Disconnect_Client_with_Reason()
{
using (var testEnvironment = CreateTestEnvironment())
{
var disconnectPacketReceived = false;
string testClientId = null;
await testEnvironment.StartServer();
testEnvironment.Server.ClientConnectedAsync += e =>
{
testClientId = e.ClientId;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
client.InspectPackage += e =>
{
if (e.Buffer.Length > 0)
{
if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4)
{
disconnectPacketReceived = true;
}
}
return CompletedTask.Instance;
};
await client.ConnectAsync(
new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort)
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
.Build()
);
await LongTestDelay();
// Test client should be connected now
Assert.IsTrue(testClientId != null);
// Have the server disconnect the client with SessionTakenOver reason; no DISCONNECT should be sent to MQTT 3.1.1 clients
await testEnvironment.Server.DisconnectClientAsync(testClientId, Protocol.MqttDisconnectReasonCode.SessionTakenOver);
await LongTestDelay();
// MQTT 3.1.1 client should not receive a disconnect packet
Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected");
}
}
void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs)
{
if (_connected.ContainsKey(eventArgs.ClientId))
+5 -5
View File
@@ -3,7 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
receiverClient.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await receiverClient.SubscribeAsync("t/+");
@@ -55,7 +55,7 @@ namespace MQTTnet.Tests.Server
});
}
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(60));
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(90));
Assert.AreEqual(100000, receivedMessages);
}
@@ -74,7 +74,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e =>
{
Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
for (var i = 0; i < 100; i++)
@@ -137,7 +137,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e =>
{
Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
for (var i = 0; i < 100; i++)
@@ -130,9 +130,7 @@ namespace MQTTnet.Tests.Server
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{
ulong topicHashMask; // not needed
bool hasWildcard; // not needed
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out topicHashMask, out hasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _);
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
}
}
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Publishing_Tests : BaseTestClass
{
[TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Publishing()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
// The client will be disconnect directly after subscribing!
server.InterceptingPublishAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
var client = await testEnvironment.ConnectClient();
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
}
}
}
}
+6 -15
View File
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
server.ClientAcknowledgedPublishPacketAsync += args =>
{
eventArgs = args;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client1 = await testEnvironment.ConnectClient();
@@ -53,7 +53,7 @@ namespace MQTTnet.Tests.Server
server.ClientAcknowledgedPublishPacketAsync += args =>
{
eventArgs = args;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client1 = await testEnvironment.ConnectClient();
@@ -88,7 +88,7 @@ namespace MQTTnet.Tests.Server
eventArgs.Add(args);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client1 = await testEnvironment.ConnectClient();
@@ -99,25 +99,16 @@ namespace MQTTnet.Tests.Server
await LongTestDelay();
Assert.AreEqual(2, eventArgs.Count);
Assert.AreEqual(1, eventArgs.Count);
var firstEvent = eventArgs[0];
Assert.IsNotNull(firstEvent);
Assert.IsNotNull(firstEvent.PublishPacket);
Assert.IsNotNull(firstEvent.AcknowledgePacket);
Assert.IsFalse(firstEvent.IsCompleted);
Assert.IsTrue(firstEvent.IsCompleted);
Assert.AreEqual("A", firstEvent.PublishPacket.Topic);
var secondEvent = eventArgs[1];
Assert.IsNotNull(secondEvent);
Assert.IsNotNull(secondEvent.PublishPacket);
Assert.IsNotNull(secondEvent.AcknowledgePacket);
Assert.IsTrue(secondEvent.IsCompleted);
Assert.AreEqual("A", secondEvent.PublishPacket.Topic);
}
}
}
@@ -65,6 +65,66 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
public async Task Downgrade_QoS_Level()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
// Add the retained message with QoS 2!
await c1.PublishAsync(
new MqttApplicationMessageBuilder().WithTopic("retained")
.WithPayload(new byte[3])
.WithRetainFlag()
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce)
.Build());
// The second client uses QoS 1 so a downgrade is required.
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
await LongTestDelay();
messageHandler.AssertReceivedCountEquals(1);
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel);
}
}
[TestMethod]
public async Task No_Upgrade_QoS_Level()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
// Add the retained message with QoS 1!
await c1.PublishAsync(
new MqttApplicationMessageBuilder().WithTopic("retained")
.WithPayload(new byte[3])
.WithRetainFlag()
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build());
// The second client uses QoS 2 so an upgrade is expected but according to the MQTT spec this is not supported!
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce });
await LongTestDelay();
messageHandler.AssertReceivedCountEquals(1);
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel);
}
}
[TestMethod]
public async Task Receive_No_Retained_Message_After_Subscribe()
{
@@ -124,7 +184,7 @@ namespace MQTTnet.Tests.Server
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.WithRetainFlag()
.Build());
await c1.DisconnectAsync();
// Subscribe using a new client.
@@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Server
publishedApplicationMessages.Add(eventArgs.ApplicationMessage);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
using (var validClient = testEnvironment.CreateClient())
@@ -157,7 +157,7 @@ namespace MQTTnet.Tests.Server
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
@@ -8,6 +8,7 @@ using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
@@ -28,7 +29,7 @@ namespace MQTTnet.Tests.Server
{
e.ReasonCode = MqttConnectReasonCode.ServerMoved;
e.ServerReference = "new_server";
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
try
+37 -61
View File
@@ -10,9 +10,8 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
@@ -29,7 +28,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer(o => o.WithPersistentSessions());
const string ClientId = "Client1";
const string clientId = "Client1";
// Create client with clean session and long session expiry interval
@@ -38,7 +37,7 @@ namespace MQTTnet.Tests.Server
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession() // start and end with clean session
.WithClientId(ClientId)
.WithClientId(clientId)
.Build());
// Disconnect; empty session should be removed from server
@@ -57,7 +56,7 @@ namespace MQTTnet.Tests.Server
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession(false) // see if there is a session
.WithClientId(ClientId)
.WithClientId(clientId)
.Build();
@@ -83,7 +82,7 @@ namespace MQTTnet.Tests.Server
server.SessionDeletedAsync += e =>
{
deletedEventFired = true;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -111,7 +110,7 @@ namespace MQTTnet.Tests.Server
e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World";
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await testEnvironment.ConnectClient();
@@ -123,8 +122,12 @@ namespace MQTTnet.Tests.Server
}
}
#if !NET452
[TestMethod]
public async Task Handle_Parallel_Connection_Attempts()
[DataRow(MqttProtocolVersion.V311)]
[DataRow(MqttProtocolVersion.V500)]
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
{
using (var testEnvironment = CreateTestEnvironment())
{
@@ -132,7 +135,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
var hasReceive = false;
@@ -146,11 +149,14 @@ namespace MQTTnet.Tests.Server
// Try to connect 50 clients at the same time.
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
await Task.Delay(5000);
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay();
Assert.AreEqual(1, connectedClients.Count);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
var sendClient = await testEnvironment.ConnectClient(option2);
@@ -161,26 +167,8 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive);
}
}
[TestMethod]
public async Task Manage_Session_MaxParallel()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var serverOptions = new MqttServerOptionsBuilder();
await testEnvironment.StartServer(serverOptions);
var options = new MqttClientOptionsBuilder().WithClientId("1");
var clients = await Task.WhenAll(Enumerable.Range(0, 10).Select(i => TryConnect(testEnvironment, options)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
}
}
#endif
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
@@ -200,7 +188,7 @@ namespace MQTTnet.Tests.Server
c.AutoAcknowledge = false;
++count;
Console.WriteLine("process");
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await subscriber.SubscribeAsync("#", qos);
@@ -237,7 +225,7 @@ namespace MQTTnet.Tests.Server
client1.DisconnectedAsync += c =>
{
disconnectReason = c.Reason;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client2 = await testEnvironment.ConnectClient(options);
@@ -263,7 +251,7 @@ namespace MQTTnet.Tests.Server
e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World";
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
server.InterceptingSubscriptionAsync += e =>
@@ -276,13 +264,13 @@ namespace MQTTnet.Tests.Server
}
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
string receivedPayload = null;
@@ -291,7 +279,7 @@ namespace MQTTnet.Tests.Server
client.ApplicationMessageReceivedAsync += e =>
{
receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var subscribeResult = await client.SubscribeAsync("x");
@@ -341,7 +329,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -349,10 +337,10 @@ namespace MQTTnet.Tests.Server
// C2 has the last will defined.
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
var c2 = await testEnvironment.ConnectClient(clientOptions);
await testEnvironment.ConnectClient(clientOptions);
// C3 will do the connection takeover.
var c3 = await testEnvironment.ConnectClient(clientOptions);
await testEnvironment.ConnectClient(clientOptions);
await Task.Delay(1000);
@@ -360,41 +348,29 @@ namespace MQTTnet.Tests.Server
}
}
async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
static async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
{
try
{
var sendClient = await testEnvironment.ConnectClient(options);
var client = await testEnvironment.ConnectClient(options).ConfigureAwait(false);
sendClient.ApplicationMessageReceivedAsync += e =>
client.ApplicationMessageReceivedAsync += e =>
{
onReceive();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
using (var subscribeTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await sendClient.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, subscribeTimeout.Token).ConfigureAwait(false);
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
}
return sendClient;
return client;
}
catch (Exception)
{
return null;
}
}
async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
{
try
{
return await testEnvironment.ConnectClient(options);
}
catch
{
return null;
}
}
}
}
+28 -12
View File
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Server
e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -46,6 +46,22 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Subscribing()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
// The client will be disconnect directly after subscribing!
server.ClientSubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
var client = await testEnvironment.ConnectClient();
await client.SubscribeAsync("#");
}
}
[TestMethod]
public async Task Enqueue_Message_After_Subscription()
{
@@ -56,7 +72,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e =>
{
server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build()));
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
@@ -81,7 +97,7 @@ namespace MQTTnet.Tests.Server
{
// Set the topic to "a" regards what the client wants to subscribe.
e.TopicFilter.Topic = "a";
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var topicAReceived = false;
@@ -99,7 +115,7 @@ namespace MQTTnet.Tests.Server
topicBReceived = true;
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await client.SubscribeAsync("b");
@@ -146,7 +162,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
for (var i = 0; i < 500; i++)
@@ -189,7 +205,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var optionsBuilder = new MqttClientSubscribeOptionsBuilder();
@@ -229,7 +245,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build());
@@ -267,7 +283,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build());
@@ -301,7 +317,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2"));
@@ -316,7 +332,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e =>
{
subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
@@ -331,7 +347,7 @@ namespace MQTTnet.Tests.Server
server.ClientUnsubscribedTopicAsync += e =>
{
unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.UnsubscribeAsync("a");
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
@@ -13,49 +13,301 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public class Subscription_TopicHash_Tests
public sealed class SubscriptionTopicHashTests
{
enum TopicHashSelector { NoWildcard, SingleWildcard, MultiWildcard };
MqttSession _clientSession;
/// <summary>
/// Even fairly regularly named topics as generated by the topic generator should result in shallow hash buckets
/// </summary>
/// <returns></returns>
[TestMethod]
public void Check_Hash_Bucket_Depth()
{
const int numPublishers = 5000;
const int numTopicsPerPublisher = 10;
TopicGenerator.Generate(
numPublishers,
numTopicsPerPublisher,
out var topicsByPublisher,
out _,
out _);
// There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ...
// Hash bucket depths should remain low.
var bucketDepths = new Dictionary<ulong, int>();
var maxBucketDepth = 0;
ulong maxBucketDepthHash = 0;
var topicsByHash = new Dictionary<ulong, List<string>>();
foreach (var t in topicsByPublisher)
{
var topics = t.Value;
foreach (var topic in topics)
{
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
bucketDepths.TryGetValue(topicHash, out var currentValue);
++currentValue;
bucketDepths[topicHash] = currentValue;
if (currentValue > maxBucketDepth)
{
maxBucketDepth = currentValue;
maxBucketDepthHash = topicHash;
}
if (!topicsByHash.TryGetValue(topicHash, out var topicList))
{
topicList = new List<string>();
topicsByHash.Add(topicHash, topicList);
}
topicList.Add(topic);
}
}
var maxDepthTopics = topicsByHash[maxBucketDepthHash];
Console.Write("Max bucket depth is " + maxBucketDepth);
// for the test case the bucket depth should be less than 100
Assert.IsTrue(maxBucketDepth < 100, "Unexpected high topic hash bucket depth");
}
MQTTnet.Server.MqttSession _clientSession;
[TestMethod]
public void Match_Hash_Test_SingleWildCard()
public void Check_Selected_Topic_Hashes()
{
var l0 = "pub0";
var l1 = "topic1";
var l2 = "+";
var l3 = "prop1";
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
CheckTopicHash("client1/building1/level1/sensor1", 0x655D4AF100000000, 0xFFFFFFFFFFFFFFFF);
CheckTopicHash("client1/building1/+/sensor1", 0x655D00F100000000, 0xFFFF00FFFFFFFFFF);
CheckTopicHash("client1/+/level1/+", 0x65004A0000000000, 0xFF00FF00FFFFFFFF);
CheckTopicHash("client1/building1/level1/#", 0x655D4A0000000000, 0xFFFFFF0000000000);
CheckTopicHash("client1/+/level1/#", 0x65004A0000000000, 0xFF00FF0000000000);
}
/// <summary>
/// Test long topic name with last level being #
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_DetectMultiLevelWildcard()
{
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
}
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
/// <summary>
/// Test long topic name with last level being +
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_DetectSingleLevelWildcard()
{
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
}
/// <summary>
/// Test long topic name with last level being #
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_MultiWildcard()
{
var sb = new StringBuilder();
const int numLevels = 8;
var levelNames = new string[numLevels];
for (var i = 0; i < numLevels; ++i)
{
if (i > 0)
{
sb.Append("/");
}
string levelName;
if (i == numLevels - 1)
{
// last one is #
levelName = "#";
}
else
{
levelName = "level" + i;
}
levelNames[i] = levelName;
sb.Append(levelName);
}
var topic = sb.ToString();
// UInt64 is limited to 8 levels
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
var hashBytes = GetBytes(topicHash);
Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch");
Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch");
Assert.AreEqual(hashBytes[2], 0, "checksum 2 mismatch");
Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch");
Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch");
Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch");
Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch");
Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch");
// all bytes should contain checksum
var count = 0;
foreach (var h in hashBytes)
{
if (count < 7)
{
Assert.AreNotEqual(h, 0, "checksum mismatch");
}
else
{
Assert.AreEqual(h, 0, "checksum mismatch");
}
// The mask should have zeroes where the wildcard and ff at the end
++count;
}
// The mask should have ff except for last level
var hashMaskBytes = GetBytes(topicHashMask);
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch");
Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch");
Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch");
Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch");
Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch");
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
count = 0;
foreach (var h in hashMaskBytes)
{
if (count < 7)
{
Assert.AreEqual(h, 0xff, "mask mismatch");
}
else
{
Assert.AreEqual(h, 0, "last mask mismatch");
}
++count;
}
}
/// <summary>
/// Test long topic name exceeding 8 levels
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_NoWildCard()
{
var sb = new StringBuilder();
const int numLevels = 9;
var levelNames = new string[numLevels];
for (var i = 0; i < numLevels; ++i)
{
if (i > 0)
{
sb.Append("/");
}
var levelName = "level" + i;
levelNames[i] = levelName;
sb.Append(levelName);
}
var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
var hashBytes = GetBytes(topicHash);
// all bytes should contain checksum
var count = 0;
foreach (var h in hashBytes)
{
Assert.AreNotEqual(h, 0, "checksum mismatch");
++count;
}
// The mask should have ff
var hashMaskBytes = GetBytes(topicHashMask);
count = 0;
foreach (var h in hashMaskBytes)
{
Assert.AreEqual(h, 0xff, "mask mismatch");
++count;
}
}
/// <summary>
/// Test long topic name with last level being +
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_SingleWildCard()
{
var sb = new StringBuilder();
const int numLevels = 8;
var levelNames = new string[numLevels];
for (var i = 0; i < numLevels; ++i)
{
if (i > 0)
{
sb.Append("/");
}
string levelName;
if (i == numLevels - 1)
{
// last one is +
levelName = "+";
}
else
{
levelName = "level" + i;
}
levelNames[i] = levelName;
sb.Append(levelName);
}
var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
var hashBytes = GetBytes(topicHash);
// all bytes should contain checksum
var count = 0;
foreach (var h in hashBytes)
{
if (count < 7)
{
Assert.AreNotEqual(h, 0, "checksum mismatch");
}
else
{
// wildcard position
Assert.AreEqual(h, 0, "checksum mismatch");
}
++count;
}
// The mask should have ff
var hashMaskBytes = GetBytes(topicHashMask);
count = 0;
foreach (var h in hashMaskBytes)
{
if (count < 7)
{
Assert.AreEqual(h, 0xff, "mask mismatch");
}
else
{
Assert.AreEqual(h, 0, "last mask mismatch");
}
++count;
}
}
[TestMethod]
@@ -65,13 +317,9 @@ namespace MQTTnet.Tests.Server
var l1 = "topic1";
var l2 = "sub1";
var l3 = "#";
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
var topic = $"{l0}/{l1}/{l2}/{l3}";
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -104,13 +352,9 @@ namespace MQTTnet.Tests.Server
var l1 = "topic1";
var l2 = "sub1";
var l3 = "prop1";
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
var topic = $"{l0}/{l1}/{l2}/{l3}";
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
@@ -137,225 +381,13 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
}
/// <summary>
/// Test long topic name with last level being #
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_MultiWildcard()
public async Task Match_Hash_Test_Search_MultiWildcard()
{
var sb = new StringBuilder();
const int NumLevels = 8;
var levelNames = new string[NumLevels];
for (var i = 0; i < NumLevels; ++i)
{
if (i > 0)
sb.Append("/");
string levelName;
if (i == NumLevels - 1)
{
// last one is #
levelName = "#";
}
else
{
levelName = "level" + i;
}
levelNames[i] = levelName;
sb.Append(levelName);
}
var topic = sb.ToString();
// UInt64 is limited to 8 levels
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
var hashBytes = GetBytes(topicHash);
// all bytes should contain checksum
int count = 0;
foreach (var h in hashBytes)
{
if (count < 7)
{
Assert.AreNotEqual(h, 0, "checksum mismatch");
}
else
{
Assert.AreEqual(h, 0, "checksum mismatch");
}
++count;
}
// The mask should have ff except for last level
var hashMaskBytes = GetBytes(topicHashMask);
count = 0;
foreach (var h in hashMaskBytes)
{
if (count < 7)
{
Assert.AreEqual(h, 0xff, "mask mismatch");
}
else
{
Assert.AreEqual(h, 0, "last mask mismatch");
}
++count;
}
}
/// <summary>
/// Test long topic name with last level being +
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_SingleWildCard()
{
var sb = new StringBuilder();
const int NumLevels = 8;
var levelNames = new string[NumLevels];
for (var i = 0; i < NumLevels; ++i)
{
if (i > 0)
sb.Append("/");
string levelName;
if (i == NumLevels - 1)
{
// last one is +
levelName = "+";
}
else
{
levelName = "level" + i;
}
levelNames[i] = levelName;
sb.Append(levelName);
}
var topic = sb.ToString();
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
var hashBytes = GetBytes(topicHash);
// all bytes should contain checksum
int count = 0;
foreach (var h in hashBytes)
{
if (count < 7)
{
Assert.AreNotEqual(h, 0, "checksum mismatch");
}
else
{
// wildcard position
Assert.AreEqual(h, 0, "checksum mismatch");
}
++count;
}
// The mask should have ff
var hashMaskBytes = GetBytes(topicHashMask);
count = 0;
foreach (var h in hashMaskBytes)
{
if (count < 7)
{
Assert.AreEqual(h, 0xff, "mask mismatch");
}
else
{
Assert.AreEqual(h, 0, "last mask mismatch");
}
++count;
}
}
/// <summary>
/// Test long topic name exceeding 8 levels
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_NoWildCard()
{
var sb = new StringBuilder();
const int NumLevels = 9;
var levelNames = new string[NumLevels];
for(var i=0; i < NumLevels; ++i)
{
if (i > 0)
sb.Append("/");
var levelName = "level" + i;
levelNames[i] = levelName;
sb.Append(levelName);
}
var topic = sb.ToString();
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
var hashBytes = GetBytes(topicHash);
// all bytes should contain checksum
int count = 0;
foreach(var h in hashBytes)
{
Assert.AreNotEqual(h, 0, "checksum mismatch");
++count;
}
// The mask should have ff
var hashMaskBytes = GetBytes(topicHashMask);
count = 0;
foreach (var h in hashMaskBytes)
{
Assert.AreEqual(h, 0xff, "mask mismatch");
++count;
}
}
/// <summary>
/// Test long topic name with last level being +
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_DetectSingleLevelWildcard()
{
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
}
/// <summary>
/// Test long topic name with last level being #
/// </summary>
[TestMethod]
public void Match_Hash_Test_LongTopic_DetectMultiLevelWildcard()
{
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard);
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard");
// Should match all topics
Assert.AreEqual(topics.Count, matchCount, "Topics not matched");
}
[TestMethod]
@@ -407,134 +439,125 @@ namespace MQTTnet.Tests.Server
}
[TestMethod]
public async Task Match_Hash_Test_Search_MultiWildcard()
public void Match_Hash_Test_SingleWildCard()
{
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard);
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard");
// Should match all topics
Assert.AreEqual(topics.Count, matchCount, "Topics not matched");
}
var l0 = "pub0";
var l1 = "topic1";
var l2 = "+";
var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}";
/// <summary>
/// Even fairly regularly named topics as generated by the topic generator should result in shallow hash buckets
/// </summary>
/// <returns></returns>
[TestMethod]
public void Check_Hash_Bucket_Depth()
{
Dictionary<string, List<string>> topicsByPublisher;
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
const int NumPublishers = 5000;
const int NumTopicsPerPublisher = 10;
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
var hashBytes = GetBytes(topicHash);
Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch");
Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch");
Assert.AreEqual(hashBytes[2], 0, "checksum 2 mismatch");
Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch");
Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch");
Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch");
Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch");
Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch");
// There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ...
// Hash bucket depths should remain low.
var bucketDepths = new Dictionary<UInt64, int>();
int maxBucketDepth = 0;
UInt64 maxBucketDepthHash = 0;
var topicsByHash = new Dictionary<UInt64, List<string>>();
foreach (var t in topicsByPublisher)
{
var topics = t.Value;
foreach (var topic in topics)
{
UInt64 topicHash;
UInt64 hashMask;
bool hasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out hashMask, out hasWildcard);
bucketDepths.TryGetValue(topicHash, out var currentValue);
++currentValue;
bucketDepths[topicHash] = currentValue;
if (currentValue > maxBucketDepth)
{
maxBucketDepth = currentValue;
maxBucketDepthHash = topicHash;
}
if (!topicsByHash.TryGetValue(topicHash, out var topicList))
{
topicList = new List<string>();
topicsByHash.Add(topicHash, topicList);
}
topicList.Add(topic);
}
}
var maxDepthTopics = topicsByHash[maxBucketDepthHash];
Console.Write("Max bucket depth is " + maxBucketDepth);
// for the test case the bucket depth should be less than 100
Assert.IsTrue(maxBucketDepth < 100, "Unexpected high topic hash bucket depth");
}
[TestMethod]
public void Check_Selected_Topic_Hashes()
{
CheckTopicHash("client1/building1/level1/sensor1", 0x655D4AF100000000, 0xFFFFFFFFFFFFFFFF);
CheckTopicHash("client1/building1/+/sensor1", 0x655D00F100000000, 0xFFFF00FFFFFFFFFF);
CheckTopicHash("client1/+/level1/+", 0x65004A0000000000, 0xFF00FF00FFFFFFFF);
CheckTopicHash("client1/building1/level1/#", 0x655D4A0000000000, 0xFFFFFF0000000000);
CheckTopicHash("client1/+/level1/#", 0x65004A0000000000, 0xFF00FF0000000000);
// The mask should have zeroes where the wildcard and ff at the end
var hashMaskBytes = GetBytes(topicHashMask);
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch");
Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch");
Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch");
Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch");
Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch");
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
}
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
{
ulong topicHash;
ulong hashMask;
bool hasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out hashMask, out hasWildcard);
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
Console.WriteLine();
Console.WriteLine("Topic: " + topic);
Console.WriteLine(string.Format("Hash: {0:X8}", topicHash));
Console.WriteLine(string.Format("Hash Mask: {0:X8}", hashMask));
Console.WriteLine($"Hash: {topicHash:X8}");
Console.WriteLine($"Hash Mask: {hashMask:X8}");
Assert.AreEqual(expectedHash, topicHash, "Topic hash not as expected. Has the hash function changed?");
Assert.AreEqual(expectedHashMask, hashMask, "Topic hash mask not as expected");
}
int CheckTopicSubscriptions(MqttSession clientSession, List<string> topicsToFind, string subject)
{
var matchCount = 0;
{
var resultCount = 0;
var stopWatch = new Stopwatch();
stopWatch.Start();
var countUp = 0;
var countDown = topicsToFind.Count - 1;
for (; countUp < topicsToFind.Count; ++countUp, --countDown)
{
var topicToFind = topicsToFind[countDown];
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _);
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
if (result.IsSubscribed)
{
++matchCount;
}
++resultCount;
}
stopWatch.Stop();
Console.Write("Match count: " + matchCount + "; ");
Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds);
}
return matchCount;
}
byte[] GetBytes(ulong value)
{
var bytes = BitConverter.GetBytes(value);
// Ensure that highest byte comes first for comparison left to right
if (BitConverter.IsLittleEndian)
{
return bytes.Reverse().ToArray();
}
return bytes;
}
async Task<List<string>> PrepareTopicHashSubscriptions(TopicHashSelector selector)
{
Dictionary<string, List<string>> topicsByPublisher;
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
const int numPublishers = 1;
const int numTopicsPerPublisher = 10000;
const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
TopicGenerator.Generate(
numPublishers,
numTopicsPerPublisher,
out var topicsByPublisher,
out var singleWildcardTopicsByPublisher,
out var multiWildcardTopicsByPublisher);
var topics = topicsByPublisher.FirstOrDefault().Value;
var singleWildcardTopics = singleWildcardTopicsByPublisher.FirstOrDefault().Value;
var multiWildcardTopics = multiWildcardTopicsByPublisher.FirstOrDefault().Value;
const string ClientId = "Client1";
var logger = new Mockups.TestLogger();
var serverOptions = new MQTTnet.Server.MqttServerOptions();
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
const string clientId = "Client1";
var logger = new TestLogger();
var serverOptions = new MqttServerOptions();
var eventContainer = new MqttServerEventContainer();
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
_clientSession = new MQTTnet.Server.MqttSession(
ClientId,
false,
new Dictionary<object, object>(),
serverOptions,
eventContainer,
retainedMessagesManager,
sessionManager
);
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
List<string> topicsToSubscribe;
@@ -550,63 +573,26 @@ namespace MQTTnet.Tests.Server
topicsToSubscribe = topics;
break;
}
foreach (var t in topicsToSubscribe)
{
var subPacket = new Packets.MqttSubscribePacket();
var filter = new Packets.MqttTopicFilter();
filter.Topic = t;
var subPacket = new MqttSubscribePacket();
var filter = new MqttTopicFilter
{
Topic = t
};
subPacket.TopicFilters.Add(filter);
await _clientSession.SubscriptionsManager.Subscribe(subPacket, default(CancellationToken));
await _clientSession.Subscribe(subPacket, default);
}
return topics;
}
int CheckTopicSubscriptions(MQTTnet.Server.MqttSession clientSession, List<string> topicsToFind, string subject)
enum TopicHashSelector
{
var matchCount = 0;
{
int resultCount = 0;
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
var countUp = 0;
var countDown = topicsToFind.Count - 1;
for (; countUp < topicsToFind.Count; ++countUp, --countDown)
{
var topicToFind = topicsToFind[countDown];
UInt64 topicHash;
UInt64 hashMask;
bool hasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash((string)topicToFind, out topicHash, out hashMask, out hasWildcard);
var result = clientSession.SubscriptionsManager.CheckSubscriptions((string)topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient");
if (result.IsSubscribed)
{
++matchCount;
}
++resultCount;
}
stopWatch.Stop();
Console.Write("Match count: " + matchCount + "; ");
Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds);
}
return matchCount;
}
byte[] GetBytes(UInt64 value)
{
var bytes = BitConverter.GetBytes(value);
// Ensure that highest byte comes first for comparison left to right
if (BitConverter.IsLittleEndian)
return bytes.Reverse().ToArray();
return bytes;
NoWildcard,
SingleWildcard,
MultiWildcard
}
}
}
}
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Server
{
@@ -50,7 +50,7 @@ namespace MQTTnet.Tests.Server
receivedTopics.Add(e.ApplicationMessage.Topic);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync("#");
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Unsubscribe_Tests : BaseTestClass
{
[TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Unsubscribing()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
// The client will be disconnect directly after subscribing!
server.ClientUnsubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
var client = await testEnvironment.ConnectClient();
await client.SubscribeAsync("#");
await client.UnsubscribeAsync("#");
}
}
}
}
@@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Tests.Mockups;
@@ -48,7 +49,7 @@ namespace MQTTnet.Tests.Server
receiver.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await sender.PublishAsync(message.Build(), CancellationToken.None);
+3 -3
View File
@@ -2,7 +2,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -53,7 +53,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
};
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
+1 -2
View File
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MQTTnet.Tests.Server
namespace MQTTnet.Tests
{
public class TopicGenerator
{
+48 -9
View File
@@ -13,7 +13,6 @@ using MQTTnet.Channel;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets;
@@ -23,7 +22,7 @@ namespace MQTTnet.Adapter
{
const uint ErrorOperationAborted = 0x800703E3;
const int ReadBufferSize = 4096;
readonly IMqttChannel _channel;
readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger;
@@ -73,7 +72,26 @@ namespace MQTTnet.Adapter
try
{
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
/*
* We have to implement a small workaround here to support connecting in Xamarin
* with a disabled WiFi network. If the WiFi is disabled the connect method will
* block forever. Even a cancellation token is not supported properly.
*/
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{
throw new OperationCanceledException("MQTT connect cancelled.", cancellationToken);
}
// Make sure that the exception from the connect task gets thrown.
await connectTask.ConfigureAwait(false);
}
}
catch (Exception exception)
{
@@ -176,7 +194,7 @@ namespace MQTTnet.Adapter
// This lock makes sure that multiple threads can send packets at the same time.
// This is required when a disconnect is sent from another thread while the
// worker thread is still sending publish packets etc.
using (await _syncRoot.WaitAsync(cancellationToken).ConfigureAwait(false))
using (await _syncRoot.EnterAsync(cancellationToken).ConfigureAwait(false))
{
// Check for cancellation here again because "WaitAsync" might take some time.
cancellationToken.ThrowIfCancellationRequested();
@@ -188,11 +206,14 @@ namespace MQTTnet.Adapter
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
await _channel.WriteAsync(packetBuffer.Packet.Array, packetBuffer.Packet.Offset, packetBuffer.Packet.Count, cancellationToken).ConfigureAwait(false);
if (packetBuffer.Payload.Count > 0)
{
await _channel.WriteAsync(packetBuffer.Payload.Array, packetBuffer.Payload.Offset, packetBuffer.Payload.Count, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
}
else
{
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
}
Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
@@ -274,8 +295,26 @@ namespace MQTTnet.Adapter
while (totalBytesRead < buffer.Length)
{
var bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
// Check two times for cancellation because the call to _ReadAsync_ might block for some time.
if (cancellationToken.IsCancellationRequested)
{
return ReadFixedHeaderResult.Cancelled;
}
int bytesRead;
try
{
bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return ReadFixedHeaderResult.Cancelled;
}
catch (SocketException)
{
return ReadFixedHeaderResult.ConnectionClosed;
}
if (cancellationToken.IsCancellationRequested)
{
return ReadFixedHeaderResult.Cancelled;
@@ -341,7 +380,7 @@ namespace MQTTnet.Adapter
var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0)
{
return new ReceivedMqttPacket(fixedHeader.Flags, PlatformAbstractionLayer.EmptyByteArraySegment, 2);
return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2);
}
var bodyLength = fixedHeader.RemainingLength;
+5 -1
View File
@@ -12,13 +12,17 @@ namespace MQTTnet.Channel
public interface IMqttChannel : IDisposable
{
string Endpoint { get; }
bool IsSecureConnection { get; }
X509Certificate2 ClientCertificate { get; }
Task ConnectAsync(CancellationToken cancellationToken);
Task DisconnectAsync(CancellationToken cancellationToken);
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,16 @@
// 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.Exceptions;
namespace MQTTnet.Client
{
public sealed class MqttClientDisconnectedException : MqttCommunicationException
{
public MqttClientDisconnectedException(Exception innerException) : base("The MQTT client is disconnected.", innerException)
{
}
}
}
@@ -3,14 +3,15 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Exceptions
namespace MQTTnet.Client
{
public class MqttUnexpectedDisconnectReceivedException : MqttCommunicationException
public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException
{
public MqttUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
: base($"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.")
{
ReasonCode = disconnectPacket.ReasonCode;
+18 -18
View File
@@ -7,32 +7,32 @@ namespace MQTTnet.Client
{
public interface IMqttClient : IDisposable
{
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
bool IsConnected { get; }
MqttClientOptions Options { get; }
Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default);
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default);
Task PingAsync(CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default);
}
}
File diff suppressed because it is too large Load Diff
@@ -14,6 +14,26 @@ namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
try
{
await client.PingAsync(cancellationToken).ConfigureAwait(false);
return true;
}
catch
{
// Ignore errors.
}
return false;
}
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
{
if (client == null)
@@ -13,80 +13,83 @@ namespace MQTTnet.Client
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
{
readonly Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> _acknowledgeHandler;
int _isAcknowledged;
public MqttApplicationMessageReceivedEventArgs(
string clientId,
string clientId,
MqttApplicationMessage applicationMessage,
MqttPublishPacket publishPacket,
Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> acknowledgeHandler)
{
ClientId = clientId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PublishPacket = publishPacket;
PacketIdentifier = publishPacket.PacketIdentifier;
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
_acknowledgeHandler = acknowledgeHandler;
}
internal MqttPublishPacket PublishPacket { get; set; }
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// Gets or sets whether the library should send MQTT ACK packets automatically if required.
/// </summary>
public bool AutoAcknowledge { get; set; } = true;
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public MqttApplicationMessage ApplicationMessage { get; }
public bool ProcessingFailed { get; set; }
/// <summary>
/// Gets or sets the reason code which will be sent to the server.
/// </summary>
public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success;
/// <summary>
/// Gets or sets the user properties which will be sent to the server in the ACK packet etc.
/// </summary>
public List<MqttUserProperty> ResponseUserProperties { get; } = new List<MqttUserProperty>();
/// <summary>
/// Gets or sets the reason string which will be sent to the server in the ACK packet.
/// </summary>
public string ResponseReasonString { get; set; }
/// <summary>
/// Gets or sets whether this message was handled.
/// This value can be used in user code for custom control flow.
/// Gets or sets whether this message was handled.
/// This value can be used in user code for custom control flow.
/// </summary>
public bool IsHandled { get; set; }
/// <summary>
/// Gets or sets whether the library should send MQTT ACK packets automatically if required.
/// Gets the identifier of the MQTT packet
/// </summary>
public bool AutoAcknowledge { get; set; } = true;
public object Tag { get; set; }
public ushort PacketIdentifier => PublishPacket.PacketIdentifier;
/// <summary>
/// Gets the indentifier of MQTT packet
/// Indicates if the processing of this PUBLISH packet has failed.
/// If the processing has failed the client will not send an ACK packet etc.
/// </summary>
public ushort PacketIdentifier { get; }
public bool ProcessingFailed { get; set; }
public Task AcknowledgeAsync(CancellationToken cancellationToken)
/// <summary>
/// Gets or sets the reason code which will be sent to the server.
/// </summary>
public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success;
/// <summary>
/// Gets or sets the reason string which will be sent to the server in the ACK packet.
/// </summary>
public string ResponseReasonString { get; set; }
/// <summary>
/// Gets or sets the user properties which will be sent to the server in the ACK packet etc.
/// </summary>
public List<MqttUserProperty> ResponseUserProperties { get; } = new List<MqttUserProperty>();
public object Tag { get; set; }
internal MqttPublishPacket PublishPacket { get; set; }
public Task AcknowledgeAsync(CancellationToken cancellationToken)
{
if (_acknowledgeHandler == null)
{
throw new NotSupportedException("Deferred acknowledgement of application message is not yet supported in MQTTnet server.");
}
if (Interlocked.CompareExchange(ref _isAcknowledged, 1, 0) == 0)
{
return _acknowledgeHandler(this, cancellationToken);
}
throw new InvalidOperationException("The application message is already acknowledged.");
}
}
}
}
@@ -8,12 +8,8 @@ namespace MQTTnet.Exceptions
{
public class MqttCommunicationException : Exception
{
protected MqttCommunicationException()
{
}
public MqttCommunicationException(Exception innerException)
: base(innerException.Message, innerException)
: base(innerException?.Message ?? "MQTT communication failed.", innerException)
{
}
+4 -4
View File
@@ -6,13 +6,13 @@ using System;
using System.Runtime.CompilerServices;
using System.Text;
using MQTTnet.Exceptions;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Formatter
{
public sealed class MqttBufferReader
{
byte[] _buffer = PlatformAbstractionLayer.EmptyByteArray;
byte[] _buffer = EmptyBuffer.Array;
int _initialPosition;
int _length;
int _position;
@@ -29,7 +29,7 @@ namespace MQTTnet.Formatter
if (length == 0)
{
return PlatformAbstractionLayer.EmptyByteArray;
return EmptyBuffer.Array;
}
ValidateReceiveBuffer(length);
@@ -66,7 +66,7 @@ namespace MQTTnet.Formatter
if (bufferLength == 0)
{
return PlatformAbstractionLayer.EmptyByteArray;
return EmptyBuffer.Array;
}
var buffer = new byte[bufferLength];
+2 -1
View File
@@ -5,12 +5,13 @@
using System;
using System.Linq;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Formatter
{
public readonly struct MqttPacketBuffer
{
static readonly ArraySegment<byte> EmptyPayload = PlatformAbstractionLayer.EmptyByteArraySegment;
static readonly ArraySegment<byte> EmptyPayload = EmptyBuffer.ArraySegment;
public MqttPacketBuffer(ArraySegment<byte> packet, ArraySegment<byte> payload)
{
@@ -7,6 +7,7 @@ namespace MQTTnet.Formatter
public sealed class MqttPacketFactories
{
public MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
public MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
public MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
@@ -5,6 +5,7 @@
using System;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Formatter
{
@@ -80,5 +81,17 @@ namespace MQTTnet.Formatter
return packet;
}
public MqttPublishPacket Create(MqttRetainedMessageMatch retainedMessage)
{
if (retainedMessage == null)
{
throw new ArgumentNullException(nameof(retainedMessage));
}
var publishPacket = Create(retainedMessage.ApplicationMessage);
publishPacket.QualityOfServiceLevel = retainedMessage.SubscriptionQualityOfServiceLevel;
return publishPacket;
}
}
}
@@ -10,16 +10,16 @@ namespace MQTTnet.Formatter
{
public sealed class MqttUnsubAckPacketFactory
{
public MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubscribeResult mqttUnsubscribeResult)
public MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, UnsubscribeResult unsubscribeResult)
{
if (unsubscribePacket == null)
{
throw new ArgumentNullException(nameof(unsubscribePacket));
}
if (mqttUnsubscribeResult == null)
if (unsubscribeResult == null)
{
throw new ArgumentNullException(nameof(mqttUnsubscribeResult));
throw new ArgumentNullException(nameof(unsubscribeResult));
}
var unsubAckPacket = new MqttUnsubAckPacket
@@ -28,7 +28,7 @@ namespace MQTTnet.Formatter
};
// MQTTv5.0.0 only.
unsubAckPacket.ReasonCodes = mqttUnsubscribeResult.ReasonCodes;
unsubAckPacket.ReasonCodes = unsubscribeResult.ReasonCodes;
return unsubAckPacket;
}
@@ -15,14 +15,20 @@ namespace MQTTnet.Implementations
public sealed class CrossPlatformSocket : IDisposable
{
readonly Socket _socket;
#if !NET5_0_OR_GREATER
readonly Action _socketDisposeAction;
#endif
NetworkStream _networkStream;
public CrossPlatformSocket(AddressFamily addressFamily)
{
_socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose;
#endif
}
public CrossPlatformSocket()
@@ -31,17 +37,76 @@ namespace MQTTnet.Implementations
// will make use of dual mode in the .net framework.
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose;
#endif
}
public CrossPlatformSocket(Socket socket)
CrossPlatformSocket(Socket socket)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_networkStream = new NetworkStream(socket, true);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose;
#endif
}
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public bool IsConnected => _socket.Connected;
public bool KeepAlive
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive) as int? == 1;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, value ? 1 : 0);
}
public int TcpKeepAliveInterval
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
#else
get { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
#endif
}
public int TcpKeepAliveRetryCount
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
#else
get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
#endif
}
public int TcpKeepAliveTime
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
#else
get { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
#endif
}
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public bool NoDelay
{
// We cannot use the _NoDelay_ property from the socket because there is an issue in .NET 4.5.2, 4.6.
@@ -51,24 +116,20 @@ namespace MQTTnet.Implementations
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0);
}
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public int ReceiveBufferSize
{
get => _socket.ReceiveBufferSize;
set => _socket.ReceiveBufferSize = value;
}
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) as int? != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public int SendBufferSize
{
get => _socket.SendBufferSize;
@@ -81,16 +142,6 @@ namespace MQTTnet.Implementations
set => _socket.SendTimeout = value;
}
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public bool IsConnected => _socket.Connected;
public async Task<CrossPlatformSocket> AcceptAsync()
{
try
@@ -109,8 +160,6 @@ namespace MQTTnet.Implementations
}
}
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public void Bind(EndPoint localEndPoint)
{
if (localEndPoint is null)
@@ -121,11 +170,6 @@ namespace MQTTnet.Implementations
_socket.Bind(localEndPoint);
}
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
}
public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken)
{
if (host is null)
@@ -137,11 +181,17 @@ namespace MQTTnet.Implementations
try
{
_networkStream?.Dispose();
#if NET5_0_OR_GREATER
if (_networkStream != null)
{
await _networkStream.DisposeAsync().ConfigureAwait(false);
}
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else
_networkStream?.Dispose();
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_socketDisposeAction))
{
@@ -175,20 +225,26 @@ namespace MQTTnet.Implementations
}
}
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
public void Dispose()
{
try
_networkStream?.Dispose();
_socket?.Dispose();
}
public NetworkStream GetStream()
{
var networkStream = _networkStream;
if (networkStream == null)
{
#if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
throw new IOException("The socket is not connected.");
}
return networkStream;
}
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
}
public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -208,28 +264,27 @@ namespace MQTTnet.Implementations
}
}
public NetworkStream GetStream()
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{
var networkStream = _networkStream;
if (networkStream == null)
try
{
throw new IOException("The socket is not connected.");
#if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
}
return networkStream;
}
public void Dispose()
{
_networkStream?.Dispose();
_socket?.Dispose();
}
#if NET452 || NET461
sealed class SocketWrapper
{
readonly Socket _socket;
readonly ArraySegment<byte> _buffer;
readonly Socket _socket;
readonly SocketFlags _socketFlags;
public SocketWrapper(Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -239,16 +294,28 @@ namespace MQTTnet.Implementations
_socketFlags = socketFlags;
}
public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
}
public static IAsyncResult BeginReceive(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginReceive(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
return socketWrapper._socket.BeginReceive(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
}
public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
}
}
#endif
@@ -108,12 +108,12 @@ namespace MQTTnet.Implementations
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
// In the write method only the internal buffer will be filled. So here is no
// async/await required. The real network transmit is done when calling the
// Flush method.
_writeStream.Write(buffer, offset, count);
_writeStream.Write(buffer.Array, buffer.Offset, buffer.Count);
return _writeStream.FlushAsync(cancellationToken);
}
@@ -14,6 +14,7 @@ using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
{
@@ -154,7 +155,7 @@ namespace MQTTnet.Implementations
public Task DisconnectAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.FromResult(0);
return CompletedTask.Instance;
}
public void Dispose()
@@ -225,7 +226,7 @@ namespace MQTTnet.Implementations
}
}
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -239,12 +240,12 @@ namespace MQTTnet.Implementations
}
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
await stream.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false);
#else
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_disposeAction))
{
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken).ConfigureAwait(false);
}
#endif
}
@@ -13,6 +13,7 @@ using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
{
@@ -56,13 +57,13 @@ namespace MQTTnet.Implementations
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
}
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public Task StopAsync()
{
Cleanup();
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
public void Dispose()
@@ -124,7 +125,7 @@ namespace MQTTnet.Implementations
var clientHandler = ClientHandler;
if (clientHandler == null)
{
return Task.FromResult(0);
return CompletedTask.Instance;
}
return clientHandler(channelAdapter);
@@ -134,7 +134,7 @@ namespace MQTTnet.Implementations
continue;
}
Task.Run(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken).RunInBackground(_logger);
_ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -2,15 +2,15 @@
// 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.Channel;
using MQTTnet.Internal;
using System;
using System.Net;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
{
@@ -35,12 +35,12 @@ namespace MQTTnet.Implementations
ClientCertificate = clientCertificate;
}
public X509Certificate2 ClientCertificate { get; }
public string Endpoint { get; }
public bool IsSecureConnection { get; private set; }
public X509Certificate2 ClientCertificate { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{
var uri = _options.Uri;
@@ -89,14 +89,26 @@ namespace MQTTnet.Implementations
Cleanup();
}
public void Dispose()
{
Cleanup();
}
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
return response.Count;
}
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET5_0_OR_GREATER
// MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1].
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
#else
// The lock is required because the client will throw an exception if _SendAsync_ is
// called from multiple threads at the same time. But this issue only happens with several
// framework versions.
@@ -105,88 +117,11 @@ namespace MQTTnet.Implementations
return;
}
using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false))
using (await _sendLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
}
}
public void Dispose()
{
Cleanup();
}
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
{
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
{
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
}
if (_options.SubProtocols != null)
{
foreach (var subProtocol in _options.SubProtocols)
{
clientWebSocket.Options.AddSubProtocol(subProtocol);
}
}
if (_options.CookieContainer != null)
{
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
#if WINDOWS_UWP
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
}
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
#if NETSTANDARD1_3
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard1.3'.");
#elif NETSTANDARD2_0
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
var context = new MqttClientCertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context);
};
#endif
}
}
void Cleanup()
@@ -230,5 +165,77 @@ namespace MQTTnet.Implementations
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
#endif
}
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
{
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
{
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
}
if (_options.SubProtocols != null)
{
foreach (var subProtocol in _options.SubProtocols)
{
clientWebSocket.Options.AddSubProtocol(subProtocol);
}
}
if (_options.CookieContainer != null)
{
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
#if WINDOWS_UWP
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
}
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
#if NETSTANDARD1_3
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard1.3'.");
#elif NETSTANDARD2_0
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
var context = new MqttClientCertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context);
};
#endif
}
}
}
}
@@ -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.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Implementations
{
public static class PlatformAbstractionLayer
{
#if NET452
public static Task CompletedTask => Task.FromResult(0);
public static byte[] EmptyByteArray { get; } = new byte[0];
#else
public static Task CompletedTask => Task.CompletedTask;
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
#endif
public static ArraySegment<byte> EmptyByteArraySegment { get; } = new ArraySegment<byte>(EmptyByteArray);
public static void Sleep(TimeSpan timeout)
{
#if !NETSTANDARD1_3 && !WINDOWS_UWP
try
{
System.Threading.Thread.Sleep(timeout);
}
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
}
}
}
+50 -8
View File
@@ -13,7 +13,16 @@ namespace MQTTnet.Internal
{
readonly List<AsyncEventInvocator<TEventArgs>> _handlers = new List<AsyncEventInvocator<TEventArgs>>();
public bool HasHandlers => _handlers.Count > 0;
ICollection<AsyncEventInvocator<TEventArgs>> _handlersForInvoke;
public AsyncEvent()
{
_handlersForInvoke = _handlers;
}
// Track the existence of handlers in a separate field so that checking it all the time will not
// require locking the actual list (_handlers).
public bool HasHandlers { get; private set; }
public void AddHandler(Func<TEventArgs, Task> handler)
{
@@ -22,7 +31,13 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
lock (_handlers)
{
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
public void AddHandler(Action<TEventArgs> handler)
@@ -32,17 +47,32 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
lock (_handlers)
{
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
public async Task InvokeAsync(TEventArgs eventArgs)
{
foreach (var handler in _handlers)
if (!HasHandlers)
{
return;
}
// Adding or removing handlers will produce a new list instance all the time.
// So locking here is not required since only the reference to an immutable list
// of handlers is used.
var handlers = _handlersForInvoke;
foreach (var handler in handlers)
{
await handler.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
public void RemoveHandler(Func<TEventArgs, Task> handler)
{
if (handler == null)
@@ -50,7 +80,13 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.RemoveAll(h => h.WrapsHandler(handler));
lock (_handlers)
{
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
public void RemoveHandler(Action<TEventArgs> handler)
@@ -60,7 +96,13 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.RemoveAll(h => h.WrapsHandler(handler));
lock (_handlers)
{
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
}
}
public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger)
@@ -81,7 +123,7 @@ namespace MQTTnet.Internal
}
catch (Exception exception)
{
logger.Warning(exception, $"Error while invoking event ({typeof(TEventArgs)}).");
logger.Warning(exception, $"Error while invoking event with arguments of type {typeof(TEventArgs)}.");
}
}
}
@@ -36,7 +36,7 @@ namespace MQTTnet.Internal
if (_handler != null)
{
_handler.Invoke(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
return CompletedTask.Instance;
}
if (_asyncHandler != null)
+121 -44
View File
@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -10,77 +12,152 @@ namespace MQTTnet.Internal
{
public sealed class AsyncLock : IDisposable
{
readonly Task<IDisposable> _completedTask;
readonly IDisposable _releaser;
readonly object _syncRoot = new object();
readonly Task<IDisposable> _releaser;
readonly Queue<AsyncLockWaiter> _waiters = new Queue<AsyncLockWaiter>(64);
SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
volatile bool _isDisposed;
bool _isLocked;
public AsyncLock()
{
_releaser = Task.FromResult((IDisposable)new Releaser(this));
}
public Task<IDisposable> WaitAsync(CancellationToken cancellationToken)
{
Task task;
// This lock is required to avoid ObjectDisposedExceptions.
// These are fired when this lock gets disposed (and thus the semaphore)
// and a worker thread tries to call this method at the same time.
// Another way would be catching all ObjectDisposedExceptions but this situation happens
// quite often when clients are disconnecting.
lock (_syncRoot)
{
task = _semaphore?.WaitAsync(cancellationToken);
}
if (task == null)
{
throw new ObjectDisposedException("The AsyncLock is disposed.");
}
if (task.Status == TaskStatus.RanToCompletion)
{
return _releaser;
}
// Wait for the _WaitAsync_ method and return the releaser afterwards.
return task.ContinueWith(
(_, state) => (IDisposable)state,
_releaser.Result,
cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
_releaser = new Releaser(this);
_completedTask = Task.FromResult(_releaser);
}
public void Dispose()
{
lock (_syncRoot)
{
_semaphore?.Dispose();
_semaphore = null;
_isDisposed = true;
while (_waiters.Any())
{
_waiters.Dequeue().Dispose();
}
}
}
internal void Release()
public Task<IDisposable> EnterAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(AsyncLock));
}
lock (_syncRoot)
{
if (!_isLocked)
{
_isLocked = true;
return _completedTask;
}
var waiter = new AsyncLockWaiter(cancellationToken);
_waiters.Enqueue(waiter);
return waiter.Task;
}
}
void Release()
{
lock (_syncRoot)
{
_semaphore?.Release();
if (_isDisposed)
{
// All waiters have been canceled with a ObjectDisposedException.
// So there is nothing left to do.
return;
}
// Assume that there is no waiter left first.
_isLocked = false;
// Try to find the next waiter which can be approved.
// Some of them might be canceled already so it is not
// guaranteed that the very next waiter is the correct one.
while (_waiters.Any())
{
var waiter = _waiters.Dequeue();
var isApproved = waiter.Approve(_releaser);
waiter.Dispose();
if (isApproved)
{
_isLocked = true;
return;
}
}
}
}
sealed class Releaser : IDisposable
sealed class AsyncLockWaiter : IDisposable
{
readonly AsyncLock _lock;
readonly CancellationTokenRegistration _cancellationRegistration;
readonly bool _hasCancellationRegistration;
readonly AsyncTaskCompletionSource<IDisposable> _promise = new AsyncTaskCompletionSource<IDisposable>();
internal Releaser(AsyncLock @lock)
public AsyncLockWaiter(CancellationToken cancellationToken)
{
_lock = @lock;
cancellationToken.ThrowIfCancellationRequested();
if (cancellationToken.CanBeCanceled)
{
_cancellationRegistration = cancellationToken.Register(Cancel);
_hasCancellationRegistration = true;
}
}
public Task<IDisposable> Task => _promise.Task;
public bool Approve(IDisposable scope)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (_promise.Task.IsCompleted)
{
return false;
}
return _promise.TrySetResult(scope);
}
public void Dispose()
{
_lock.Release();
if (_hasCancellationRegistration)
{
_cancellationRegistration.Dispose();
}
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncLockWaiter)));
}
void Cancel()
{
_promise.TrySetCanceled();
}
}
readonly struct Releaser : IDisposable
{
readonly AsyncLock _asyncLock;
public Releaser(AsyncLock asyncLock)
{
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock));
}
public void Dispose()
{
_asyncLock.Release();
}
}
}
}
}
+54 -55
View File
@@ -11,64 +11,39 @@ namespace MQTTnet.Internal
{
public sealed class AsyncQueue<TItem> : IDisposable
{
readonly AsyncSignal _signal = new AsyncSignal();
readonly object _syncRoot = new object();
SemaphoreSlim _semaphore = new SemaphoreSlim(0);
ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>();
bool _isDisposed;
public int Count => _queue.Count;
public void Clear()
{
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>());
}
public void Dispose()
{
lock (_syncRoot)
{
_signal?.Dispose();
_isDisposed = true;
}
}
public void Enqueue(TItem item)
{
lock (_syncRoot)
{
_queue.Enqueue(item);
_semaphore?.Release();
_signal.Set();
}
}
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
Task task;
lock (_syncRoot)
{
if (_semaphore == null)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
task = _semaphore.WaitAsync(cancellationToken);
}
await task.ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
if (_queue.TryDequeue(out var item))
{
return new AsyncQueueDequeueResult<TItem>(true, item);
}
}
catch (ArgumentNullException)
{
// The semaphore throws this internally sometimes.
return new AsyncQueueDequeueResult<TItem>(false, default);
}
catch (OperationCanceledException)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
}
return new AsyncQueueDequeueResult<TItem>(false, default);
}
public AsyncQueueDequeueResult<TItem> TryDequeue()
{
if (_queue.TryDequeue(out var item))
@@ -79,18 +54,42 @@ namespace MQTTnet.Internal
return new AsyncQueueDequeueResult<TItem>(false, default);
}
public void Clear()
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
{
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>());
}
public void Dispose()
{
lock (_syncRoot)
while (!cancellationToken.IsCancellationRequested)
{
_semaphore?.Dispose();
_semaphore = null;
try
{
Task task;
lock (_syncRoot)
{
if (_isDisposed)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
task = _signal.WaitAsync(cancellationToken);
}
await task.ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
if (_queue.TryDequeue(out var item))
{
return new AsyncQueueDequeueResult<TItem>(true, item);
}
}
catch (OperationCanceledException)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
}
return new AsyncQueueDequeueResult<TItem>(false, default);
}
}
}
}
+113
View File
@@ -0,0 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Internal
{
public sealed class AsyncSignal : IDisposable
{
readonly object _syncRoot = new object();
bool _isDisposed;
bool _isSignaled;
AsyncSignalWaiter _waiter;
public void Dispose()
{
lock (_syncRoot)
{
_waiter?.Dispose();
_waiter = null;
_isDisposed = true;
}
}
public void Set()
{
lock (_syncRoot)
{
_isSignaled = true;
// If there is already a waiting task let it run.
_waiter?.Approve();
_waiter = null;
}
}
public Task WaitAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
lock (_syncRoot)
{
ThrowIfDisposed();
if (_isSignaled)
{
return CompletedTask.Instance;
}
if (_waiter != null)
{
if (!_waiter.Task.IsCompleted)
{
throw new InvalidOperationException("Only one waiting task is permitted per async signal.");
}
_waiter.Dispose();
}
_waiter = new AsyncSignalWaiter(cancellationToken);
return _waiter.Task;
}
}
void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(AsyncSignal));
}
}
sealed class AsyncSignalWaiter : IDisposable
{
readonly AsyncTaskCompletionSource<bool> _promise = new AsyncTaskCompletionSource<bool>();
// ReSharper disable once FieldCanBeMadeReadOnly.Local
CancellationTokenRegistration _cancellationTokenRegistration;
public AsyncSignalWaiter(CancellationToken cancellationToken)
{
if (cancellationToken.CanBeCanceled)
{
_cancellationTokenRegistration = cancellationToken.Register(Cancel);
}
}
public Task Task => _promise.Task;
public void Approve()
{
_promise.TrySetResult(true);
}
public void Dispose()
{
_cancellationTokenRegistration.Dispose();
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter)));
}
void Cancel()
{
_promise.TrySetCanceled();
}
}
}
}

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