mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-23 22:04:43 +08:00
Compare commits
41
Commits
v4.0.2.221
...
v4.1.1.318
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f74af45fba | ||
|
|
e050e2d61e | ||
|
|
ef85ef676a | ||
|
|
e977546297 | ||
|
|
ae92b0ed33 | ||
|
|
7e686d9aa5 | ||
|
|
d23bbf7bf3 | ||
|
|
d4dad4860a | ||
|
|
c009cbef76 | ||
|
|
37e60c73ae | ||
|
|
3ab095a568 | ||
|
|
f5d9b65139 | ||
|
|
31e5b40459 | ||
|
|
8fb55d75fe | ||
|
|
420222511b | ||
|
|
c5cec60809 | ||
|
|
12dd815421 | ||
|
|
f36f49cb3e | ||
|
|
9152fe637e | ||
|
|
b976e9c177 | ||
|
|
262a4681ad | ||
|
|
ad546adafe | ||
|
|
29a9941d86 | ||
|
|
5a21939e29 | ||
|
|
469caf656e | ||
|
|
42402fba04 | ||
|
|
a0dc8ab0d7 | ||
|
|
02ede5a2d8 | ||
|
|
cef61900df | ||
|
|
09385b1b36 | ||
|
|
cbf413f04e | ||
|
|
11249d006f | ||
|
|
0d7b020994 | ||
|
|
d71ff3d615 | ||
|
|
57745e8324 | ||
|
|
a94776eeea | ||
|
|
d3848d8cb0 | ||
|
|
6c29ea56fc | ||
|
|
ed7c150cc5 | ||
|
|
13762eedd4 | ||
|
|
e33495010e |
@@ -1,8 +1,7 @@
|
|||||||
* [Core] Updated nuget packages.
|
* [Core] MQTT Packets being sent over web socket transport are now setting the web socket frame boundaries correctly (#1499).
|
||||||
* [Core] The option _IgnoreCertificateChainErrors_ is now respected (thanks to @GodVenn, #1447).
|
* [Core] Add support for attaching and detaching events from different threads.
|
||||||
* [ManagedClient] The managed client now sends the entire topic filter including new MQTTv5 properties when subscribing.
|
* [Core] Fixed a deadlock in _AsyncLock_ implementation (#1520).
|
||||||
* [ManagedClient] Fixed wrong firing of _ApplicationMessageSkippedAsync_ event (thanks to @quackgizmo, #1460).
|
* [Client] Keep alive mechanism now uses the configured timeout value from the options (thanks to @Stannieman, #1495).
|
||||||
* [Server] Fixed reporting of _MaximumQoS_ in _ConnAck_ packet (MQTTv5 only) (#1442).
|
* [Client] The _PingAsync_ will fallback to the timeout specified in the client options when the cancellation token cannot be cancelled.
|
||||||
* [Server] Fix cross thread issue in session message storage for QoS 1 and 2.
|
* [Server] A DISCONNECT packet is no longer sent to MQTT clients < 5.0.0 (thanks to @logicaloud, #1506).
|
||||||
* [Server] The event _ClientSubscribedTopicAsync_ is now fired after the subscription is completely processed internally (#1435).
|
* [Server] Improved "take over" process handling.
|
||||||
* [Server] Improved CPU usage on lower end machines (#788).
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.0.2.${{github.run_number}}"
|
VERSION: "4.1.1.${{github.run_number}}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?>
|
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?>
|
||||||
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
||||||
<TypePattern DisplayName="Non-reorderable types">
|
<TypePattern DisplayName="Non-reorderable types">
|
||||||
<TypePattern.Match>
|
<TypePattern.Match>
|
||||||
<Or>
|
<Or>
|
||||||
@@ -210,6 +210,7 @@
|
|||||||
</Or>
|
</Or>
|
||||||
</Entry.Match>
|
</Entry.Match>
|
||||||
<Entry.SortBy>
|
<Entry.SortBy>
|
||||||
|
<Access Order="Public Internal Protected Private" />
|
||||||
<Name />
|
<Name />
|
||||||
</Entry.SortBy>
|
</Entry.SortBy>
|
||||||
</Entry>
|
</Entry>
|
||||||
@@ -226,6 +227,9 @@
|
|||||||
</Entry>
|
</Entry>
|
||||||
</TypePattern>
|
</TypePattern>
|
||||||
</Patterns></s:String>
|
</Patterns></s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">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.</s:String>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
@@ -237,5 +241,6 @@
|
|||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
# MQTTnet
|
# 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
|
## 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
|
* A custom message interceptor can be added which allows transforming or extending every received application message
|
||||||
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
||||||
|
|
||||||
## 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
|
## Code of Conduct
|
||||||
|
|
||||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||||
|
|||||||
@@ -2,8 +2,13 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Helpers;
|
||||||
|
|
||||||
@@ -150,6 +155,30 @@ public static class Client_Connection_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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().UseWebSocket4Net();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
||||||
|
|
||||||
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
|
||||||
|
response.DumpToConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Connect_Client_With_TLS_Encryption()
|
public static async Task Connect_Client_With_TLS_Encryption()
|
||||||
{
|
{
|
||||||
@@ -292,14 +321,12 @@ public static class Client_Connection_Samples
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required
|
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||||
// in the first place.
|
if (!await mqttClient.TryPingAsync())
|
||||||
if (!mqttClient.IsConnected)
|
|
||||||
{
|
{
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
// Subscribe to topics when session is clean etc.
|
// Subscribe to topics when session is clean etc.
|
||||||
|
|
||||||
Console.WriteLine("The MQTT client is connected.");
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
@@ -36,6 +40,53 @@ public static class Client_Publish_Samples
|
|||||||
|
|
||||||
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
|
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.");
|
Console.WriteLine("MQTT application message is published.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Helpers;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
@@ -77,4 +82,48 @@ public static class Client_Subscribe_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Diagnostics;
|
namespace MQTTnet.Samples.Diagnostics;
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.ManagedClient;
|
using MQTTnet.Extensions.ManagedClient;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.Rpc;
|
using MQTTnet.Extensions.Rpc;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// 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 Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_Asp_Net_Samples
|
||||||
|
{
|
||||||
|
public static Task Start_Server_With_WebSockets_Support()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a minimal ASP.NET Webserver including a hosted MQTT server.
|
||||||
|
*/
|
||||||
|
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
|
||||||
|
.ConfigureWebHostDefaults(
|
||||||
|
webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseKestrel(
|
||||||
|
o =>
|
||||||
|
{
|
||||||
|
// This will allow MQTT connections based on TCP port 1883.
|
||||||
|
o.ListenAnyIP(1883, l => l.UseMqtt());
|
||||||
|
|
||||||
|
// This will allow MQTT connections based on HTTP WebSockets with URI "localhost:5000/mqtt"
|
||||||
|
// See code below for URI configuration.
|
||||||
|
o.ListenAnyIP(5000); // Default HTTP pipeline
|
||||||
|
});
|
||||||
|
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return host.RunConsoleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class MqttController
|
||||||
|
{
|
||||||
|
public MqttController()
|
||||||
|
{
|
||||||
|
// Inject other services via constructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
||||||
|
{
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseEndpoints(
|
||||||
|
endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapConnectionHandler<MqttConnectionHandler>(
|
||||||
|
"/mqtt",
|
||||||
|
httpConnectionDispatcherOptions => httpConnectionDispatcherOptions.WebSockets.SubProtocolSelector =
|
||||||
|
protocolList => protocolList.FirstOrDefault() ?? string.Empty);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseMqttServer(
|
||||||
|
server =>
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Attach event handlers etc. if required.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
|
||||||
|
server.ClientConnectedAsync += mqttController.OnClientConnected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHostedMqttServer(
|
||||||
|
optionsBuilder =>
|
||||||
|
{
|
||||||
|
optionsBuilder.WithDefaultEndpoint();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddMqttConnectionHandler();
|
||||||
|
services.AddConnections();
|
||||||
|
|
||||||
|
services.AddSingleton<MqttController>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
@@ -134,7 +138,7 @@ public static class Server_Simple_Samples
|
|||||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Username != "ValidUser")
|
if (e.UserName != "ValidUser")
|
||||||
{
|
{
|
||||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
}
|
}
|
||||||
@@ -212,4 +216,4 @@ public static class Server_Simple_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.WaitAsync(CancellationToken.None).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.WaitAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
|
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Channel;
|
using MQTTnet.Channel;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
@@ -70,8 +71,8 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
while (read < expected)
|
while (read < expected)
|
||||||
{
|
{
|
||||||
var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
|
||||||
read += readresult;
|
read += readResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +80,11 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
|
var buffer = new ArraySegment<byte>(new byte[size]);
|
||||||
|
|
||||||
for (var i = 0; i < iterations; i++)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
Console.WriteLine("c = SubscribeBenchmark");
|
Console.WriteLine("c = SubscribeBenchmark");
|
||||||
Console.WriteLine("d = UnsubscribeBenchmark");
|
Console.WriteLine("d = UnsubscribeBenchmark");
|
||||||
Console.WriteLine("e = MessageDeliveryBenchmark");
|
Console.WriteLine("e = MessageDeliveryBenchmark");
|
||||||
|
Console.WriteLine("f = AsyncLockBenchmark");
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
var pressedKey = Console.ReadKey(true);
|
||||||
switch (pressedKey.KeyChar)
|
switch (pressedKey.KeyChar)
|
||||||
@@ -75,6 +76,9 @@ namespace MQTTnet.Benchmarks
|
|||||||
case 'e':
|
case 'e':
|
||||||
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
|
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
|
||||||
break;
|
break;
|
||||||
|
case 'f':
|
||||||
|
BenchmarkRunner.Run<AsyncLockBenchmark>();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
return Task.FromResult(count);
|
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();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
#else
|
#else
|
||||||
certificates.Add(certificate);
|
certificates.Add(certificate);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +154,9 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return Task.FromResult(readBytes);
|
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);
|
_webSocket.Send(buffer.Array, buffer.Offset, buffer.Count);
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.WaitAsync(cancellationToken.Token).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ namespace MQTTnet.TestApp
|
|||||||
Console.WriteLine("c = Start QoS 0 benchmark");
|
Console.WriteLine("c = Start QoS 0 benchmark");
|
||||||
Console.WriteLine("d = Start server with logging");
|
Console.WriteLine("d = Start server with logging");
|
||||||
Console.WriteLine("e = Start Message Throughput Test");
|
Console.WriteLine("e = Start Message Throughput Test");
|
||||||
|
Console.WriteLine("f = Start AsyncLock Test");
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
var pressedKey = Console.ReadKey(true);
|
||||||
if (pressedKey.KeyChar == '1')
|
if (pressedKey.KeyChar == '1')
|
||||||
@@ -88,6 +89,10 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
Task.Run(new MessageThroughputTest().Run);
|
Task.Run(new MessageThroughputTest().Run);
|
||||||
}
|
}
|
||||||
|
else if (pressedKey.KeyChar == 'f')
|
||||||
|
{
|
||||||
|
Task.Run(new AsyncLockTest().Run);
|
||||||
|
}
|
||||||
|
|
||||||
Thread.Sleep(Timeout.Infinite);
|
Thread.Sleep(Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
if (e.ClientId == "SpecialClient")
|
if (e.ClientId == "SpecialClient")
|
||||||
{
|
{
|
||||||
if (e.Username != "USER" || e.Password != "PASS")
|
if (e.UserName != "USER" || e.Password != "PASS")
|
||||||
{
|
{
|
||||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ using MQTTnet.Packets;
|
|||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Client
|
namespace MQTTnet.Tests.Client
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class Client_Tests : BaseTestClass
|
public sealed class MqttClient_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||||
@@ -145,6 +147,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
@@ -239,10 +242,10 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
for (var i = 0; i < 98; i++)
|
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, clientStatus.Count);
|
||||||
Assert.AreEqual(1, sessionStatus.Count);
|
Assert.AreEqual(1, sessionStatus.Count);
|
||||||
@@ -332,7 +335,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
var client = await testEnvironment.ConnectClient();
|
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();
|
await testEnvironment.StartServer();
|
||||||
var client = await testEnvironment.ConnectClient();
|
var client = await testEnvironment.ConnectClient();
|
||||||
Assert.IsTrue(client.IsConnected);
|
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
|
||||||
|
|
||||||
await client.DisconnectAsync();
|
await client.DisconnectAsync();
|
||||||
|
|
||||||
Assert.IsFalse(client.IsConnected);
|
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,17 +594,17 @@ namespace MQTTnet.Tests.Client
|
|||||||
var client = await testEnvironment.ConnectClient();
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
Assert.IsTrue(client.IsConnected);
|
Assert.IsTrue(await client.TryPingAsync());
|
||||||
|
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
Assert.IsFalse(client.IsConnected);
|
Assert.IsFalse(await client.TryPingAsync());
|
||||||
|
|
||||||
await server.StartAsync();
|
await server.StartAsync();
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
|
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
|
catch
|
||||||
{
|
{
|
||||||
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
SpinWait.SpinUntil(() => tries >= maxTries, 10000);
|
SpinWait.SpinUntil(() => tries >= maxTries, 10000);
|
||||||
@@ -656,11 +660,11 @@ namespace MQTTnet.Tests.Client
|
|||||||
var client = await testEnvironment.ConnectClient();
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
Assert.IsTrue(client.IsConnected);
|
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
|
||||||
|
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
Assert.IsFalse(client.IsConnected);
|
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult());
|
||||||
|
|
||||||
for (var i = 0; i < 5; i++)
|
for (var i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
@@ -671,6 +675,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +683,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
|
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
|
||||||
Assert.IsTrue(client.IsConnected);
|
await client.PingAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,10 +833,10 @@ namespace MQTTnet.Tests.Client
|
|||||||
{
|
{
|
||||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||||
{
|
{
|
||||||
var server = await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
var client = await testEnvironment.ConnectClient();
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
Assert.IsTrue(client.IsConnected);
|
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
|
||||||
client.DisconnectedAsync += e =>
|
client.DisconnectedAsync += e =>
|
||||||
{
|
{
|
||||||
Assert.IsTrue(e.ClientWasConnected);
|
Assert.IsTrue(e.ClientWasConnected);
|
||||||
@@ -851,7 +856,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
var server = await testEnvironment.StartServer();
|
var server = await testEnvironment.StartServer();
|
||||||
var client = await testEnvironment.ConnectClient();
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
Assert.IsTrue(client.IsConnected);
|
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
|
||||||
client.DisconnectedAsync += e =>
|
client.DisconnectedAsync += e =>
|
||||||
{
|
{
|
||||||
Assert.IsTrue(e.ClientWasConnected);
|
Assert.IsTrue(e.ClientWasConnected);
|
||||||
@@ -941,7 +946,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
Assert.IsTrue(messageReceived);
|
Assert.IsTrue(messageReceived);
|
||||||
Assert.IsTrue(client1.IsConnected);
|
Assert.IsTrue(client1.TryPingAsync().GetAwaiter().GetResult());
|
||||||
Assert.IsFalse(disconnectedFired);
|
Assert.IsFalse(disconnectedFired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Internal
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class AsyncEvent_Tests
|
||||||
|
{
|
||||||
|
int _testEventAsyncCount;
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Attach_EventHandler()
|
||||||
|
{
|
||||||
|
var testClass = new TestClass();
|
||||||
|
|
||||||
|
testClass.TestEventAsync += OnTestEventAsync;
|
||||||
|
|
||||||
|
await testClass.FireEventAsync(EventArgs.Empty);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, _testEventAsyncCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Detach_EventHandler()
|
||||||
|
{
|
||||||
|
var testClass = new TestClass();
|
||||||
|
|
||||||
|
testClass.TestEventAsync += OnTestEventAsync;
|
||||||
|
testClass.TestEventAsync -= OnTestEventAsync;
|
||||||
|
|
||||||
|
await testClass.FireEventAsync(EventArgs.Empty);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task OnTestEventAsync(EventArgs arg)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _testEventAsyncCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TestClass
|
||||||
|
{
|
||||||
|
readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -13,6 +14,80 @@ namespace MQTTnet.Tests.Internal
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class AsyncLock_Tests
|
public class AsyncLock_Tests
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Lock_10_Parallel_Tasks()
|
||||||
|
{
|
||||||
|
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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(threads);
|
||||||
|
Assert.AreEqual(ThreadsCount, 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.WaitAsync(CancellationToken.None))
|
||||||
|
{
|
||||||
|
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]
|
[TestMethod]
|
||||||
public async Task Lock_Serial_Calls()
|
public async Task Lock_Serial_Calls()
|
||||||
{
|
{
|
||||||
@@ -45,58 +120,32 @@ namespace MQTTnet.Tests.Internal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//[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]
|
[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 releaser = await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
var @lock = new AsyncLock();
|
|
||||||
var globalI = 0;
|
try
|
||||||
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
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
|
||||||
threads[i] = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
using (var releaser = await @lock.WaitAsync(CancellationToken.None))
|
await asyncLock.WaitAsync(timeout.Token).ConfigureAwait(false);
|
||||||
{
|
}
|
||||||
var localI = globalI;
|
|
||||||
await Task.Delay(10); // Increase the chance for wrong data.
|
Assert.Fail("Exception should be thrown!");
|
||||||
localI++;
|
}
|
||||||
globalI = localI;
|
catch (OperationCanceledException)
|
||||||
}
|
{
|
||||||
});
|
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.WaitAll(threads);
|
releaser.Dispose();
|
||||||
Assert.AreEqual(ThreadsCount, globalI);
|
|
||||||
|
using (await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
// When the method finished, the thread got access.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +175,49 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Disconnect_with_Reason()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var disconnectReason = MQTTnet.Client.MqttClientDisconnectReason.UnspecifiedError;
|
||||||
|
|
||||||
|
string testClientId = null;
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
testEnvironment.Server.ClientConnectedAsync += e =>
|
||||||
|
{
|
||||||
|
testClientId = e.ClientId;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient(new MQTTnet.Client.MqttClientOptionsBuilder().WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500));
|
||||||
|
|
||||||
|
client.DisconnectedAsync += e =>
|
||||||
|
{
|
||||||
|
disconnectReason = e.Reason;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
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, Protocol.MqttDisconnectReasonCode.AdministrativeAction);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// The reason should be returned to the client in the DISCONNECT packet
|
||||||
|
|
||||||
|
Assert.AreEqual(MQTTnet.Client.MqttClientDisconnectReason.AdministrativeAction, disconnectReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
|
MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
|
||||||
{
|
{
|
||||||
return testEnvironment.Factory.CreateClientOptionsBuilder()
|
return testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -45,9 +46,9 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
|
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()
|
public void Dispose()
|
||||||
|
|||||||
@@ -798,38 +798,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0]));
|
Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Session_Takeover()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var options = new MqttClientOptionsBuilder().WithCleanSession(false)
|
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+
|
|
||||||
.WithClientId("a");
|
|
||||||
|
|
||||||
var client1 = await testEnvironment.ConnectClient(options);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
|
||||||
client1.DisconnectedAsync += c =>
|
|
||||||
{
|
|
||||||
disconnectReason = c.Reason;
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
var client2 = await testEnvironment.ConnectClient(options);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
Assert.IsFalse(client1.IsConnected);
|
|
||||||
Assert.IsTrue(client2.IsConnected);
|
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Set_Subscription_At_Server()
|
public async Task Set_Subscription_At_Server()
|
||||||
{
|
{
|
||||||
@@ -944,26 +913,6 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Use_Clean_Session()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build());
|
|
||||||
|
|
||||||
// Create the session including the subscription.
|
|
||||||
var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false));
|
|
||||||
await client1.SubscribeAsync("x");
|
|
||||||
await client1.DisconnectAsync();
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
Assert.IsFalse(connectResult.IsSessionPresent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Use_Empty_Client_ID()
|
public async Task Use_Empty_Client_ID()
|
||||||
{
|
{
|
||||||
@@ -988,93 +937,57 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
public async Task Disconnect_Client_with_Reason()
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
var receivedMessagesCount = 0;
|
var disconnectPacketReceived = false;
|
||||||
|
|
||||||
|
string testClientId = null;
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
testEnvironment.Server.ClientConnectedAsync += e =>
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
testClientId = e.ClientId;
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
client.InspectPackage += e =>
|
||||||
await c2.DisconnectAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(0, receivedMessagesCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Will_Message_Do_Not_Send_On_Takeover()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
var receivedMessagesCount = 0;
|
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
// C1 will receive the last will!
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
if (e.Buffer.Length > 0)
|
||||||
|
{
|
||||||
|
if (e.Buffer[0] == (byte)Protocol.MqttControlPacketType.Disconnect << 4)
|
||||||
|
{
|
||||||
|
disconnectPacketReceived = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
await client.ConnectAsync(
|
||||||
|
new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort)
|
||||||
|
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
|
||||||
|
.Build()
|
||||||
|
);
|
||||||
|
|
||||||
// C2 has the last will defined.
|
await LongTestDelay();
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
|
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
// Test client should be connected now
|
||||||
|
|
||||||
// C3 will do the connection takeover.
|
Assert.IsTrue(testClientId != null);
|
||||||
var c3 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
// Have the server disconnect the client with SessionTakenOver reason; no DISCONNECT should be sent to MQTT 3.1.1 clients
|
||||||
|
|
||||||
Assert.AreEqual(0, receivedMessagesCount);
|
await testEnvironment.Server.DisconnectClientAsync(testClientId, Protocol.MqttDisconnectReasonCode.SessionTakenOver);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
await LongTestDelay();
|
||||||
public async Task Will_Message_Send()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
// MQTT 3.1.1 client should not receive a disconnect packet
|
||||||
|
|
||||||
var receivedMessagesCount = 0;
|
Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected");
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedMessagesCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class QoS_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_0()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
ClientAcknowledgedPublishPacketEventArgs eventArgs = null;
|
||||||
|
server.ClientAcknowledgedPublishPacketAsync += args =>
|
||||||
|
{
|
||||||
|
eventArgs = args;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
|
await client1.SubscribeAsync("A");
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient();
|
||||||
|
await client2.PublishStringAsync("A");
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Must be null because no event should be fired for QoS 0.
|
||||||
|
Assert.IsNull(eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_1()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
ClientAcknowledgedPublishPacketEventArgs eventArgs = null;
|
||||||
|
server.ClientAcknowledgedPublishPacketAsync += args =>
|
||||||
|
{
|
||||||
|
eventArgs = args;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
|
await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient();
|
||||||
|
await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.IsNotNull(eventArgs);
|
||||||
|
Assert.IsNotNull(eventArgs.PublishPacket);
|
||||||
|
Assert.IsNotNull(eventArgs.AcknowledgePacket);
|
||||||
|
Assert.IsTrue(eventArgs.IsCompleted);
|
||||||
|
|
||||||
|
Assert.AreEqual("A", eventArgs.PublishPacket.Topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_2()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var eventArgs = new List<ClientAcknowledgedPublishPacketEventArgs>();
|
||||||
|
server.ClientAcknowledgedPublishPacketAsync += args =>
|
||||||
|
{
|
||||||
|
lock (eventArgs)
|
||||||
|
{
|
||||||
|
eventArgs.Add(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
|
await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient();
|
||||||
|
await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.AreEqual(2, eventArgs.Count);
|
||||||
|
|
||||||
|
var firstEvent = eventArgs[0];
|
||||||
|
|
||||||
|
Assert.IsNotNull(firstEvent);
|
||||||
|
Assert.IsNotNull(firstEvent.PublishPacket);
|
||||||
|
Assert.IsNotNull(firstEvent.AcknowledgePacket);
|
||||||
|
Assert.IsFalse(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,8 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -40,7 +38,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
|
testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
|
||||||
{
|
{
|
||||||
if (eventArgs.Username == "SECRET")
|
if (eventArgs.UserName == "SECRET")
|
||||||
{
|
{
|
||||||
eventArgs.ReasonCode = MqttConnectReasonCode.Success;
|
eventArgs.ReasonCode = MqttConnectReasonCode.Success;
|
||||||
}
|
}
|
||||||
@@ -149,7 +147,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
server.ValidatingConnectionAsync += e =>
|
server.ValidatingConnectionAsync += e =>
|
||||||
{
|
{
|
||||||
if (e.Username != "UserName1")
|
if (e.UserName != "UserName1")
|
||||||
{
|
{
|
||||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ using MQTTnet.Implementations;
|
|||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
using MqttClient = MQTTnet.Client.MqttClient;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
@@ -125,7 +124,9 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[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())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
@@ -133,7 +134,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
await testEnvironment.StartServer();
|
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).WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
var hasReceive = false;
|
var hasReceive = false;
|
||||||
|
|
||||||
@@ -147,7 +148,10 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
// Try to connect 50 clients at the same time.
|
// 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 clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
||||||
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
var connectedClients = clients.Where(c => c != null).Where(c => c.TryPingAsync().GetAwaiter().GetResult()).ToList();
|
||||||
|
|
||||||
Assert.AreEqual(1, connectedClients.Count);
|
Assert.AreEqual(1, connectedClients.Count);
|
||||||
|
|
||||||
@@ -162,26 +166,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, hasReceive);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||||
@@ -220,6 +205,37 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Session_Takeover()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var options = new MqttClientOptionsBuilder().WithCleanSession(false)
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+
|
||||||
|
.WithClientId("a");
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient(options);
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
var disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
||||||
|
client1.DisconnectedAsync += c =>
|
||||||
|
{
|
||||||
|
disconnectReason = c.Reason;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient(options);
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
Assert.IsFalse(client1.IsConnected);
|
||||||
|
Assert.IsTrue(client2.IsConnected);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Set_Session_Item()
|
public async Task Set_Session_Item()
|
||||||
{
|
{
|
||||||
@@ -277,7 +293,60 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
|
[TestMethod]
|
||||||
|
public async Task Use_Clean_Session()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build());
|
||||||
|
|
||||||
|
// Create the session including the subscription.
|
||||||
|
var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false));
|
||||||
|
await client1.SubscribeAsync("x");
|
||||||
|
await client1.DisconnectAsync();
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
Assert.IsFalse(connectResult.IsSessionPresent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Will_Message_Do_Not_Send_On_Takeover()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// C1 will receive the last will!
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
c1.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
|
// C2 has the last will defined.
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
|
||||||
|
|
||||||
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
|
||||||
|
// C3 will do the connection takeover.
|
||||||
|
var c3 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, receivedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -296,13 +365,13 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
return sendClient;
|
return sendClient;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
|
static async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Will_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
c1.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
await c2.DisconnectAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, receivedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Will_Message_Send()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
||||||
|
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
c1.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, receivedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,11 +188,14 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
|
_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)
|
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);
|
Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ namespace MQTTnet.Channel
|
|||||||
public interface IMqttChannel : IDisposable
|
public interface IMqttChannel : IDisposable
|
||||||
{
|
{
|
||||||
string Endpoint { get; }
|
string Endpoint { get; }
|
||||||
|
|
||||||
bool IsSecureConnection { get; }
|
bool IsSecureConnection { get; }
|
||||||
|
|
||||||
X509Certificate2 ClientCertificate { get; }
|
X509Certificate2 ClientCertificate { get; }
|
||||||
|
|
||||||
Task ConnectAsync(CancellationToken cancellationToken);
|
Task ConnectAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task DisconnectAsync(CancellationToken cancellationToken);
|
Task DisconnectAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<int> ReadAsync(byte[] buffer, int offset, int count, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,31 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public interface IMqttClient : IDisposable
|
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<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
||||||
|
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
||||||
|
|
||||||
|
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
|
||||||
|
|
||||||
|
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||||
|
|
||||||
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
||||||
|
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
MqttClientOptions Options { get; }
|
MqttClientOptions Options { get; }
|
||||||
|
|
||||||
Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default);
|
Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default);
|
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task PingAsync(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<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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+431
-400
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,26 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public static class MqttClientExtensions
|
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)
|
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
||||||
{
|
{
|
||||||
if (client == null)
|
if (client == null)
|
||||||
|
|||||||
@@ -264,8 +264,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider creating _MqttClientTcpOptionsBuilder_ as overload.
|
|
||||||
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
||||||
{
|
{
|
||||||
if (optionsBuilder == null)
|
if (optionsBuilder == null)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
@@ -10,8 +11,19 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
|
public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the local endpoint (network card) which is used by the client.
|
||||||
|
/// Set it to _null_ to let the OS select the network card.
|
||||||
|
/// </summary>
|
||||||
|
public EndPoint LocalEndpoint { get; set; }
|
||||||
|
|
||||||
public int BufferSize { get; set; } = 8192;
|
public int BufferSize { get; set; } = 8192;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the underlying socket should run in dual mode.
|
||||||
|
/// Leaving this _null_ will avoid setting this value at socket level.
|
||||||
|
/// Setting this a value other than _null_ will throw an exception when only IPv4 is supported on the machine.
|
||||||
|
/// </summary>
|
||||||
public bool? DualMode { get; set; }
|
public bool? DualMode { get; set; }
|
||||||
|
|
||||||
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
|
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
|
||||||
|
|||||||
@@ -7,23 +7,23 @@ using System.Net;
|
|||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
||||||
{
|
{
|
||||||
public string Uri { get; set; }
|
public CookieContainer CookieContainer { get; set; }
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||||
|
|
||||||
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
||||||
|
|
||||||
public CookieContainer CookieContainer { get; set; }
|
|
||||||
|
|
||||||
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
|
||||||
|
|
||||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||||
|
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Uri;
|
return Uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public class MqttClientWebSocketProxyOptions
|
public sealed class MqttClientWebSocketProxyOptions
|
||||||
{
|
{
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -13,74 +13,83 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
|
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
readonly Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> _acknowledgeHandler;
|
readonly Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> _acknowledgeHandler;
|
||||||
|
|
||||||
int _isAcknowledged;
|
int _isAcknowledged;
|
||||||
|
|
||||||
public MqttApplicationMessageReceivedEventArgs(
|
public MqttApplicationMessageReceivedEventArgs(
|
||||||
string clientId,
|
string clientId,
|
||||||
MqttApplicationMessage applicationMessage,
|
MqttApplicationMessage applicationMessage,
|
||||||
MqttPublishPacket publishPacket,
|
MqttPublishPacket publishPacket,
|
||||||
Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> acknowledgeHandler)
|
Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> acknowledgeHandler)
|
||||||
{
|
{
|
||||||
ClientId = clientId;
|
ClientId = clientId;
|
||||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
PublishPacket = publishPacket;
|
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
|
||||||
_acknowledgeHandler = acknowledgeHandler;
|
_acknowledgeHandler = acknowledgeHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal MqttPublishPacket PublishPacket { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets or sets whether the library should send MQTT ACK packets automatically if required.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// </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>
|
/// </summary>
|
||||||
public string ClientId { get; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; }
|
|
||||||
|
|
||||||
public bool ProcessingFailed { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the reason code which will be sent to the server.
|
/// Gets or sets whether this message was handled.
|
||||||
/// </summary>
|
/// This value can be used in user code for custom control flow.
|
||||||
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.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsHandled { get; set; }
|
public bool IsHandled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets ir sets whether the library should send MQTT ACK packets automatically if required.
|
/// Gets the identifier of the MQTT packet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoAcknowledge { get; set; } = true;
|
public ushort PacketIdentifier => PublishPacket.PacketIdentifier;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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 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 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; }
|
public object Tag { get; set; }
|
||||||
|
|
||||||
public Task AcknowledgeAsync(CancellationToken cancellationToken)
|
internal MqttPublishPacket PublishPacket { get; set; }
|
||||||
|
|
||||||
|
public Task AcknowledgeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_acknowledgeHandler == null)
|
if (_acknowledgeHandler == null)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Deferred acknowledgement of application message is not yet supported in MQTTnet server.");
|
throw new NotSupportedException("Deferred acknowledgement of application message is not yet supported in MQTTnet server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref _isAcknowledged, 1, 0) == 0)
|
if (Interlocked.CompareExchange(ref _isAcknowledged, 1, 0) == 0)
|
||||||
{
|
{
|
||||||
return _acknowledgeHandler(this, cancellationToken);
|
return _acknowledgeHandler(this, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException("The application message is already acknowledged.");
|
throw new InvalidOperationException("The application message is already acknowledged.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,12 +108,12 @@ namespace MQTTnet.Implementations
|
|||||||
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
|
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
|
// 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
|
// async/await required. The real network transmit is done when calling the
|
||||||
// Flush method.
|
// Flush method.
|
||||||
_writeStream.Write(buffer, offset, count);
|
_writeStream.Write(buffer.Array, buffer.Offset, buffer.Count);
|
||||||
return _writeStream.FlushAsync(cancellationToken);
|
return _writeStream.FlushAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ namespace MQTTnet.Implementations
|
|||||||
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily);
|
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_tcpOptions.LocalEndpoint != null)
|
||||||
|
{
|
||||||
|
socket.Bind(_tcpOptions.LocalEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
|
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
|
||||||
socket.SendBufferSize = _tcpOptions.BufferSize;
|
socket.SendBufferSize = _tcpOptions.BufferSize;
|
||||||
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
|
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
|
||||||
@@ -220,7 +225,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();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@@ -234,12 +239,12 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
|
#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
|
#else
|
||||||
// Workaround for: https://github.com/dotnet/corefx/issues/24430
|
// Workaround for: https://github.com/dotnet/corefx/issues/24430
|
||||||
using (cancellationToken.Register(_disposeAction))
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ namespace MQTTnet.Implementations
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken).RunInBackground(_logger);
|
_ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Channel;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Channel;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
@@ -35,12 +35,12 @@ namespace MQTTnet.Implementations
|
|||||||
ClientCertificate = clientCertificate;
|
ClientCertificate = clientCertificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate { get; }
|
||||||
|
|
||||||
public string Endpoint { get; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
public bool IsSecureConnection { get; private set; }
|
public bool IsSecureConnection { get; private set; }
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate { get; }
|
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var uri = _options.Uri;
|
var uri = _options.Uri;
|
||||||
@@ -89,14 +89,26 @@ namespace MQTTnet.Implementations
|
|||||||
Cleanup();
|
Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
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);
|
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||||
return response.Count;
|
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
|
// 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
|
// called from multiple threads at the same time. But this issue only happens with several
|
||||||
// framework versions.
|
// framework versions.
|
||||||
@@ -107,87 +119,9 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false))
|
using (await _sendLock.WaitAsync(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
|
#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()
|
void Cleanup()
|
||||||
@@ -231,5 +165,77 @@ namespace MQTTnet.Implementations
|
|||||||
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
|
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
|
||||||
#endif
|
#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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,11 @@ namespace MQTTnet.Implementations
|
|||||||
public static class PlatformAbstractionLayer
|
public static class PlatformAbstractionLayer
|
||||||
{
|
{
|
||||||
#if NET452
|
#if NET452
|
||||||
public static Task CompletedTask => Task.FromResult(0);
|
public static Task CompletedTask { get; } = Task.FromResult(0);
|
||||||
|
|
||||||
public static byte[] EmptyByteArray { get; } = new byte[0];
|
public static byte[] EmptyByteArray { get; } = new byte[0];
|
||||||
#else
|
#else
|
||||||
public static Task CompletedTask => Task.CompletedTask;
|
public static Task CompletedTask { get; } = Task.CompletedTask;
|
||||||
|
|
||||||
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
|
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
@@ -13,7 +14,16 @@ namespace MQTTnet.Internal
|
|||||||
{
|
{
|
||||||
readonly List<AsyncEventInvocator<TEventArgs>> _handlers = new List<AsyncEventInvocator<TEventArgs>>();
|
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)
|
public void AddHandler(Func<TEventArgs, Task> handler)
|
||||||
{
|
{
|
||||||
@@ -22,7 +32,13 @@ namespace MQTTnet.Internal
|
|||||||
throw new ArgumentNullException(nameof(handler));
|
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.ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddHandler(Action<TEventArgs> handler)
|
public void AddHandler(Action<TEventArgs> handler)
|
||||||
@@ -32,17 +48,32 @@ namespace MQTTnet.Internal
|
|||||||
throw new ArgumentNullException(nameof(handler));
|
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.ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InvokeAsync(TEventArgs eventArgs)
|
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);
|
await handler.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveHandler(Func<TEventArgs, Task> handler)
|
public void RemoveHandler(Func<TEventArgs, Task> handler)
|
||||||
{
|
{
|
||||||
if (handler == null)
|
if (handler == null)
|
||||||
@@ -50,7 +81,13 @@ namespace MQTTnet.Internal
|
|||||||
throw new ArgumentNullException(nameof(handler));
|
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.ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveHandler(Action<TEventArgs> handler)
|
public void RemoveHandler(Action<TEventArgs> handler)
|
||||||
@@ -60,7 +97,13 @@ namespace MQTTnet.Internal
|
|||||||
throw new ArgumentNullException(nameof(handler));
|
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.ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger)
|
public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger)
|
||||||
|
|||||||
@@ -21,12 +21,14 @@ namespace MQTTnet.Internal
|
|||||||
|
|
||||||
public bool WrapsHandler(Action<TEventArgs> handler)
|
public bool WrapsHandler(Action<TEventArgs> handler)
|
||||||
{
|
{
|
||||||
return ReferenceEquals(_handler, handler);
|
// Do not use ReferenceEquals! It will not work with delegates.
|
||||||
|
return handler == _handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WrapsHandler(Func<TEventArgs, Task> handler)
|
public bool WrapsHandler(Func<TEventArgs, Task> handler)
|
||||||
{
|
{
|
||||||
return ReferenceEquals(_asyncHandler, handler);
|
// Do not use ReferenceEquals! It will not work with delegates.
|
||||||
|
return handler == _asyncHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InvokeAsync(TEventArgs eventArgs)
|
public Task InvokeAsync(TEventArgs eventArgs)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -10,77 +12,174 @@ namespace MQTTnet.Internal
|
|||||||
{
|
{
|
||||||
public sealed class AsyncLock : IDisposable
|
public sealed class AsyncLock : IDisposable
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* This async supporting lock does not support reentrancy!
|
||||||
|
*/
|
||||||
|
|
||||||
|
readonly List<Releaser> _queuedTasks = new List<Releaser>(64);
|
||||||
|
readonly Task<IDisposable> _releaserTaskWithDirectApproval;
|
||||||
|
|
||||||
|
readonly Releaser _releaserWithDirectApproval;
|
||||||
|
|
||||||
readonly object _syncRoot = new object();
|
readonly object _syncRoot = new object();
|
||||||
readonly Task<IDisposable> _releaser;
|
|
||||||
|
|
||||||
SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
|
bool _isDisposed;
|
||||||
|
|
||||||
public AsyncLock()
|
public AsyncLock()
|
||||||
{
|
{
|
||||||
_releaser = Task.FromResult((IDisposable)new Releaser(this));
|
_releaserWithDirectApproval = new Releaser(this, null, CancellationToken.None);
|
||||||
}
|
_releaserTaskWithDirectApproval = Task.FromResult((IDisposable)_releaserWithDirectApproval);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
{
|
{
|
||||||
_semaphore?.Dispose();
|
foreach (var waitingTask in _queuedTasks)
|
||||||
_semaphore = null;
|
{
|
||||||
|
waitingTask.Fail(new ObjectDisposedException(nameof(AsyncLock)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_queuedTasks.Clear();
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Release()
|
public Task<IDisposable> WaitAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var hasDirectApproval = false;
|
||||||
|
Releaser releaser;
|
||||||
|
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(AsyncLock));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queuedTasks.Count == 0)
|
||||||
|
{
|
||||||
|
// There is no other waiting task apart from the current one.
|
||||||
|
// So we can approve the current task directly.
|
||||||
|
releaser = _releaserWithDirectApproval;
|
||||||
|
hasDirectApproval = true;
|
||||||
|
Debug.WriteLine("AsyncLock: Task -1 directly approved.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
releaser = new Releaser(this, new TaskCompletionSource<IDisposable>(), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
_queuedTasks.Add(releaser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDirectApproval)
|
||||||
|
{
|
||||||
|
return releaser.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _releaserTaskWithDirectApproval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release(Releaser releaser)
|
||||||
{
|
{
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
{
|
{
|
||||||
_semaphore?.Release();
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
// There is no much left to do!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeTask = _queuedTasks[0];
|
||||||
|
if (!ReferenceEquals(activeTask, releaser))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The active task must be the current releaser.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_queuedTasks.RemoveAt(0);
|
||||||
|
|
||||||
|
while (_queuedTasks.Count > 0)
|
||||||
|
{
|
||||||
|
var nextTask = _queuedTasks[0];
|
||||||
|
if (!nextTask.IsPending)
|
||||||
|
{
|
||||||
|
// Dequeue all canceled or failed tasks.
|
||||||
|
_queuedTasks.RemoveAt(0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTask.Approve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine("AsyncLock: No Task pending.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Releaser : IDisposable
|
sealed class Releaser : IDisposable
|
||||||
{
|
{
|
||||||
readonly AsyncLock _lock;
|
readonly AsyncLock _asyncLock;
|
||||||
|
readonly CancellationToken _cancellationToken;
|
||||||
|
readonly int _id;
|
||||||
|
readonly TaskCompletionSource<IDisposable> _promise;
|
||||||
|
|
||||||
internal Releaser(AsyncLock @lock)
|
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||||
|
CancellationTokenRegistration _cancellationTokenRegistration;
|
||||||
|
|
||||||
|
internal Releaser(AsyncLock asyncLock, TaskCompletionSource<IDisposable> promise, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_lock = @lock;
|
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock));
|
||||||
|
_promise = promise;
|
||||||
|
_cancellationToken = cancellationToken;
|
||||||
|
|
||||||
|
if (cancellationToken.CanBeCanceled)
|
||||||
|
{
|
||||||
|
_cancellationTokenRegistration = cancellationToken.Register(Cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
_id = promise?.Task.Id ?? -1;
|
||||||
|
|
||||||
|
Debug.WriteLine($"AsyncLock: Task {_id} queued.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPending => _promise != null && !_promise.Task.IsCanceled && !_promise.Task.IsFaulted && !_promise.Task.IsCompleted;
|
||||||
|
|
||||||
|
public Task<IDisposable> Task => _promise?.Task;
|
||||||
|
|
||||||
|
public void Approve()
|
||||||
|
{
|
||||||
|
_promise?.TrySetResult(this);
|
||||||
|
|
||||||
|
Debug.WriteLine($"AsyncLock: Task {_id} approved.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_lock.Release();
|
if (_cancellationToken.CanBeCanceled)
|
||||||
|
{
|
||||||
|
_cancellationTokenRegistration.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"AsyncLock: Task {_id} completed.");
|
||||||
|
|
||||||
|
_asyncLock.Release(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Fail(Exception exception)
|
||||||
|
{
|
||||||
|
_promise?.TrySetException(exception);
|
||||||
|
|
||||||
|
Debug.WriteLine($"AsyncLock: Task {_id} failed ({exception.GetType().Name}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cancel()
|
||||||
|
{
|
||||||
|
_promise?.TrySetCanceled();
|
||||||
|
|
||||||
|
Debug.WriteLine($"AsyncLock: Task {_id} canceled.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,14 +8,20 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
|
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
|
||||||
|
{
|
||||||
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
SenderId = senderId;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the application message which was not consumed by any client.
|
/// Gets the application message which was not consumed by any client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttApplicationMessage ApplicationMessage { get; internal set; }
|
public MqttApplicationMessage ApplicationMessage { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the client which has sent the affected application message.
|
/// Gets the ID of the client which has sent the affected application message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SenderId { get; internal set; }
|
public string SenderId { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
|
||||||
|
/// </summary>
|
||||||
|
public MqttPacketWithIdentifier AcknowledgePacket { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the client which acknowledged a PUBLISH packet.
|
||||||
|
/// </summary>
|
||||||
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the PUBLISH packet which was acknowledged.
|
||||||
|
/// </summary>
|
||||||
|
public MqttPublishPacket PublishPacket { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the session items which contain custom user data per session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,31 +3,46 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientConnectedEventArgs : EventArgs
|
public sealed class ClientConnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
|
||||||
/// Gets the client identifier of the connected client.
|
{
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
/// </summary>
|
UserName = userName;
|
||||||
public string ClientId { get; internal set; }
|
ProtocolVersion = protocolVersion;
|
||||||
|
Endpoint = endpoint;
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the user name of the connected client.
|
/// Gets the client identifier of the connected client.
|
||||||
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserName { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the protocol version which is used by the connected client.
|
/// Gets the endpoint of the connected client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttProtocolVersion ProtocolVersion { get; internal set; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the endpoint of the connected client.
|
/// Gets the protocol version which is used by the connected client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Endpoint { get; internal set; }
|
public MqttProtocolVersion ProtocolVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user name of the connected client.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,19 +3,33 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientDisconnectedEventArgs : EventArgs
|
public sealed class ClientDisconnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public ClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType, string endpoint, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
DisconnectType = disconnectType;
|
||||||
|
Endpoint = endpoint;
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
public MqttClientDisconnectType DisconnectType { get; internal set; }
|
public MqttClientDisconnectType DisconnectType { get; }
|
||||||
|
|
||||||
public string Endpoint { get; internal set; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,22 +3,35 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientSubscribedTopicEventArgs : EventArgs
|
public sealed class ClientSubscribedTopicEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public ClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter, IDictionary sessionItems)
|
||||||
/// Gets the client identifier.
|
{
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
/// </summary>
|
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||||
public string ClientId { get; internal set; }
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the topic filter.
|
/// Gets the client identifier.
|
||||||
/// The topic filter can contain topics and wildcards.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttTopicFilter TopicFilter { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the topic filter.
|
||||||
|
/// The topic filter can contain topics and wildcards.
|
||||||
|
/// </summary>
|
||||||
|
public MqttTopicFilter TopicFilter { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,34 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
|
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public ClientUnsubscribedTopicEventArgs(string clientId, string topicFilter, IDictionary sessionItems)
|
||||||
/// Gets the client identifier.
|
{
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
/// </summary>
|
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||||
public string ClientId { get; internal set; }
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the topic filter.
|
/// Gets the client identifier.
|
||||||
/// The topic filter can contain topics and wildcards.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TopicFilter { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the topic filter.
|
||||||
|
/// The topic filter can contain topics and wildcards.
|
||||||
|
/// </summary>
|
||||||
|
public string TopicFilter { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
@@ -10,30 +11,44 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingPacketEventArgs : EventArgs
|
public sealed class InterceptingPacketEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string endpoint, MqttPacket packet, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
Endpoint = endpoint;
|
||||||
|
Packet = packet ?? throw new ArgumentNullException(nameof(packet));
|
||||||
|
SessionItems = sessionItems;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client ID which has sent the packet or will receive the packet.
|
/// Gets the cancellation token from the connection managing thread.
|
||||||
|
/// Use this in further event processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the endpoint of the sending or receiving client.
|
/// Gets the client ID which has sent the packet or will receive the packet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Endpoint { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MQTT packet which was received or will be sent.
|
/// Gets the endpoint of the sending or receiving client.
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the MQTT packet which was received or will be sent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttPacket Packet { get; set; }
|
public MqttPacket Packet { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the packet should be processed or not.
|
/// Gets or sets whether the packet should be processed or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ProcessPacket { get; set; } = true;
|
public bool ProcessPacket { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token from the connection managing thread.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// Use this in further event processing.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,18 +10,26 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingPublishEventArgs : EventArgs
|
public sealed class InterceptingPublishEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token which can indicate that the client connection gets down.
|
/// Gets the cancellation token which can indicate that the client connection gets down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
public bool CloseConnection { get; set; }
|
public bool CloseConnection { get; set; }
|
||||||
|
|
||||||
@@ -38,6 +46,6 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary SessionItems { get; internal set; }
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,16 +12,28 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingSubscriptionEventArgs : EventArgs
|
public sealed class InterceptingSubscriptionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingSubscriptionEventArgs(
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
string clientId,
|
||||||
|
MqttSessionStatus session,
|
||||||
|
MqttTopicFilter topicFilter)
|
||||||
|
{
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId;
|
||||||
|
Session = session;
|
||||||
|
TopicFilter = topicFilter;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token which can indicate that the client connection gets down.
|
/// Gets the cancellation token which can indicate that the client connection gets down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the broker should close the client connection.
|
/// Gets or sets whether the broker should close the client connection.
|
||||||
@@ -48,12 +60,12 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current client session.
|
/// Gets the current client session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttSessionStatus Session { get; internal set; }
|
public MqttSessionStatus Session { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary SessionItems { get; internal set; }
|
public IDictionary SessionItems => Session.Items;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the topic filter.
|
/// Gets or sets the topic filter.
|
||||||
|
|||||||
@@ -10,16 +10,24 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic)
|
||||||
|
{
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId;
|
||||||
|
SessionItems = sessionItems;
|
||||||
|
Topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token which can indicate that the client connection gets down.
|
/// Gets the cancellation token which can indicate that the client connection gets down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the broker should close the client connection.
|
/// Gets or sets whether the broker should close the client connection.
|
||||||
@@ -41,7 +49,7 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary SessionItems { get; internal set; }
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MQTT topic.
|
/// Gets or sets the MQTT topic.
|
||||||
@@ -50,6 +58,6 @@ namespace MQTTnet.Server
|
|||||||
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level
|
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level
|
||||||
/// separator).
|
/// separator).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Topic { get; internal set; }
|
public string Topic { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,17 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class RetainedMessageChangedEventArgs : EventArgs
|
public sealed class RetainedMessageChangedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public string ClientId { get; internal set; }
|
public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List<MqttApplicationMessage> storedRetainedMessages)
|
||||||
|
{
|
||||||
public MqttApplicationMessage ChangedRetainedMessage { get; internal set; }
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage));
|
||||||
public List<MqttApplicationMessage> StoredRetainedMessages { get; internal set; }
|
StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessage ChangedRetainedMessage { get; }
|
||||||
|
|
||||||
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
public List<MqttApplicationMessage> StoredRetainedMessages { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,14 +3,26 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class SessionDeletedEventArgs : EventArgs
|
public sealed class SessionDeletedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public SessionDeletedEventArgs(string id, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
Id = id ?? throw new ArgumentNullException(nameof(id));
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the session.
|
/// Gets the ID of the session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Id { get; internal set; }
|
public string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,13 +17,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class ValidatingConnectionEventArgs : EventArgs
|
public sealed class ValidatingConnectionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
readonly IMqttChannelAdapter _clientAdapter;
|
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttConnectPacket _connectPacket;
|
||||||
|
|
||||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
|
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
|
||||||
{
|
{
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,6 +43,12 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
||||||
|
/// TCP or WebSockets) or a custom implementation.
|
||||||
|
/// </summary>
|
||||||
|
public IMqttChannelAdapter ChannelAdapter { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether clean sessions are used or not.
|
/// Gets or sets a value indicating whether clean sessions are used or not.
|
||||||
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
||||||
@@ -56,7 +61,7 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? CleanSession => _connectPacket.CleanSession;
|
public bool? CleanSession => _connectPacket.CleanSession;
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate => _clientAdapter.ClientCertificate;
|
public X509Certificate2 ClientCertificate => ChannelAdapter.ClientCertificate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
@@ -64,9 +69,9 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId => _connectPacket.ClientId;
|
public string ClientId => _connectPacket.ClientId;
|
||||||
|
|
||||||
public string Endpoint => _clientAdapter.Endpoint;
|
public string Endpoint => ChannelAdapter.Endpoint;
|
||||||
|
|
||||||
public bool IsSecureConnection => _clientAdapter.IsSecureConnection;
|
public bool IsSecureConnection => ChannelAdapter.IsSecureConnection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the keep alive period.
|
/// Gets or sets the keep alive period.
|
||||||
@@ -85,7 +90,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
|
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
|
||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion;
|
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
||||||
|
|
||||||
public byte[] RawPassword => _connectPacket.Password;
|
public byte[] RawPassword => _connectPacket.Password;
|
||||||
|
|
||||||
@@ -161,7 +166,10 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
|
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
|
||||||
|
|
||||||
|
[Obsolete("This property name has a typo. Use 'UserName' instead. This one will be removed soon.")]
|
||||||
public string Username => _connectPacket.Username;
|
public string Username => _connectPacket.Username;
|
||||||
|
|
||||||
|
public string UserName => _connectPacket.Username;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the user properties.
|
/// Gets or sets the user properties.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using MQTTnet.Client;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.PacketDispatcher;
|
using MQTTnet.PacketDispatcher;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -20,20 +21,20 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class MqttClient
|
public sealed class MqttClient
|
||||||
{
|
{
|
||||||
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
||||||
|
readonly MqttConnectPacket _connectPacket;
|
||||||
|
readonly MqttServerEventContainer _eventContainer;
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
||||||
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
|
||||||
|
|
||||||
readonly MqttClientSessionsManager _sessionsManager;
|
|
||||||
readonly MqttNetSourceLogger _logger;
|
|
||||||
|
|
||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
readonly MqttServerEventContainer _eventContainer;
|
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttClientSessionsManager _sessionsManager;
|
||||||
|
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
||||||
|
|
||||||
CancellationTokenSource _cancellationToken;
|
CancellationTokenSource _cancellationToken;
|
||||||
|
|
||||||
public MqttClient(
|
public MqttClient(
|
||||||
MqttConnectPacket connectPacket,
|
MqttConnectPacket connectPacket,
|
||||||
IMqttChannelAdapter channelAdapter,
|
IMqttChannelAdapter channelAdapter,
|
||||||
@@ -53,43 +54,31 @@ namespace MQTTnet.Server
|
|||||||
Session = session ?? throw new ArgumentNullException(nameof(session));
|
Session = session ?? throw new ArgumentNullException(nameof(session));
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
|
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
_logger = logger.WithSource(nameof(MqttClient));
|
_logger = logger.WithSource(nameof(MqttClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id => _connectPacket.ClientId;
|
public IMqttChannelAdapter ChannelAdapter { get; }
|
||||||
|
|
||||||
public string Endpoint { get; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
public IMqttChannelAdapter ChannelAdapter { get; }
|
public string Id => _connectPacket.ClientId;
|
||||||
|
|
||||||
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
|
public bool IsCleanDisconnect { get; private set; }
|
||||||
|
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
|
public bool IsTakenOver { get; set; }
|
||||||
|
|
||||||
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
|
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
|
||||||
|
|
||||||
public MqttSession Session { get; }
|
public MqttSession Session { get; }
|
||||||
|
|
||||||
public bool IsTakenOver { get; set; }
|
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
|
||||||
|
|
||||||
public bool IsCleanDisconnect { get; private set; }
|
|
||||||
|
|
||||||
public async Task StopAsync(MqttDisconnectReasonCode reason)
|
|
||||||
{
|
|
||||||
IsRunning = false;
|
|
||||||
|
|
||||||
if (reason == MqttDisconnectReasonCode.SessionTakenOver || reason == MqttDisconnectReasonCode.KeepAliveTimeout)
|
|
||||||
{
|
|
||||||
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
|
|
||||||
// token because the entire connection is closed (disposed) as soon as the cancellation
|
|
||||||
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
|
||||||
// at the client!
|
|
||||||
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
StopInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetStatistics()
|
public void ResetStatistics()
|
||||||
{
|
{
|
||||||
@@ -109,8 +98,8 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Task.Run(() => SendPacketsLoop(cancellationToken), cancellationToken).RunInBackground(_logger);
|
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
|
||||||
|
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
|
||||||
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
||||||
@@ -130,10 +119,10 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
|
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
|
||||||
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
|
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
|
||||||
|
|
||||||
_= _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
|
_ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
|
||||||
Session.WillMessageSent = true;
|
Session.WillMessageSent = true;
|
||||||
|
|
||||||
_logger.Info("Client '{0}': Published will message.", Id);
|
_logger.Info("Client '{0}': Published will message.", Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,33 +138,240 @@ namespace MQTTnet.Server
|
|||||||
// This might break the protocol but the user wants that.
|
// This might break the protocol but the user wants that.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
||||||
Statistics.HandleSentPacket(packet);
|
Statistics.HandleSentPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(MqttDisconnectReasonCode reason)
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
|
// Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+.
|
||||||
|
if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
||||||
|
{
|
||||||
|
// The Client or Server MAY send a DISCONNECT packet before closing the Network Connection.
|
||||||
|
// This library does not sent a DISCONNECT packet for a normal disconnection. Maybe adding
|
||||||
|
// a configuration option is requested in the future.
|
||||||
|
if (reason != MqttDisconnectReasonCode.NormalDisconnection)
|
||||||
|
{
|
||||||
|
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
|
||||||
|
// token because the entire connection is closed (disposed) as soon as the cancellation
|
||||||
|
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
||||||
|
// at the client!
|
||||||
|
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StopInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ClientAcknowledgedPublishPacket(MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
|
||||||
|
{
|
||||||
|
if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
var eventArgs = new ClientAcknowledgedPublishPacketEventArgs
|
||||||
|
{
|
||||||
|
PublishPacket = publishPacket,
|
||||||
|
AcknowledgePacket = acknowledgePacket,
|
||||||
|
ClientId = Id,
|
||||||
|
SessionItems = Session.Items
|
||||||
|
};
|
||||||
|
|
||||||
|
return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
|
||||||
|
{
|
||||||
|
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
|
||||||
|
|
||||||
|
if (acknowledgedPublishPacket != null)
|
||||||
|
{
|
||||||
|
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket)
|
||||||
|
{
|
||||||
|
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
|
||||||
|
|
||||||
|
if (acknowledgedPublishPacket != null)
|
||||||
|
{
|
||||||
|
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
HandleTopicAlias(publishPacket);
|
||||||
|
|
||||||
|
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
|
||||||
|
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
||||||
|
var closeConnection = false;
|
||||||
|
var processPublish = true;
|
||||||
|
|
||||||
|
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, Id, Session.Items);
|
||||||
|
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
||||||
|
{
|
||||||
|
// This can happen if a topic alias us used but the topic is
|
||||||
|
// unknown to the server.
|
||||||
|
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
||||||
|
interceptingPublishEventArgs.ProcessPublish = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
|
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||||
|
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
||||||
|
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeConnection)
|
||||||
|
{
|
||||||
|
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processPublish && applicationMessage != null)
|
||||||
|
{
|
||||||
|
await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (publishPacket.QualityOfServiceLevel)
|
||||||
|
{
|
||||||
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
|
{
|
||||||
|
// Do nothing since QoS 0 has no ACK at all!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
|
{
|
||||||
|
var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||||
|
{
|
||||||
|
var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket)
|
||||||
|
{
|
||||||
|
var acknowledgedPublishPacket = Session.PeekAcknowledgePublishPacket(pubRecPacket.PacketIdentifier);
|
||||||
|
|
||||||
|
if (acknowledgedPublishPacket != null)
|
||||||
|
{
|
||||||
|
await ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubRecPacket).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||||
|
{
|
||||||
|
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
|
||||||
|
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
|
||||||
|
|
||||||
|
if (subscribeResult.CloseConnection)
|
||||||
|
{
|
||||||
|
StopInternal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscribeResult.RetainedMessages != null)
|
||||||
|
{
|
||||||
|
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages)
|
||||||
|
{
|
||||||
|
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage);
|
||||||
|
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var unsubscribeResult = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
|
||||||
|
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
|
||||||
|
|
||||||
|
if (unsubscribeResult.CloseConnection)
|
||||||
|
{
|
||||||
|
StopInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleTopicAlias(MqttPublishPacket publishPacket)
|
||||||
|
{
|
||||||
|
if (publishPacket.TopicAlias == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_topicAlias)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(publishPacket.Topic))
|
||||||
|
{
|
||||||
|
_topicAlias[publishPacket.TopicAlias] = publishPacket.Topic;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic))
|
||||||
|
{
|
||||||
|
publishPacket.Topic = topic;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task<MqttPacket> InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
async Task<MqttPacket> InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers)
|
if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
|
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
|
||||||
{
|
|
||||||
ClientId = Id,
|
|
||||||
Endpoint = Endpoint,
|
|
||||||
Packet = packet,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
|
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return interceptingPacketEventArgs.Packet;
|
return interceptingPacketEventArgs.Packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,30 +385,36 @@ namespace MQTTnet.Server
|
|||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (packet == null)
|
if (packet == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var processPacket = true;
|
|
||||||
|
|
||||||
|
// Check for cancellation again because receive packet might block some time.
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TCP connection of this client may be still open but the client has already been taken over by
|
||||||
|
// a new TCP connection. So we must exit here to make sure to no longer process any message.
|
||||||
|
if (IsTakenOver || !IsRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var processPacket = true;
|
||||||
|
|
||||||
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
|
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
|
||||||
{
|
|
||||||
ClientId = Id,
|
|
||||||
Endpoint = Endpoint,
|
|
||||||
Packet = packet,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||||
packet = interceptingPacketEventArgs.Packet;
|
packet = interceptingPacketEventArgs.Packet;
|
||||||
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!processPacket || packet == null)
|
if (!processPacket || packet == null)
|
||||||
{
|
{
|
||||||
// Restart the receiving process to get the next packet ignoring the current one..
|
// Restart the receiving process to get the next packet ignoring the current one..
|
||||||
@@ -227,15 +429,15 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
else if (packet is MqttPubAckPacket pubAckPacket)
|
else if (packet is MqttPubAckPacket pubAckPacket)
|
||||||
{
|
{
|
||||||
Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
|
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubCompPacket pubCompPacket)
|
else if (packet is MqttPubCompPacket pubCompPacket)
|
||||||
{
|
{
|
||||||
Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
|
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubRecPacket pubRecPacket)
|
else if (packet is MqttPubRecPacket pubRecPacket)
|
||||||
{
|
{
|
||||||
HandleIncomingPubRecPacket(pubRecPacket);
|
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubRelPacket pubRelPacket)
|
else if (packet is MqttPubRelPacket pubRelPacket)
|
||||||
{
|
{
|
||||||
@@ -304,6 +506,11 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsTakenOver || !IsRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false);
|
await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -353,7 +560,7 @@ namespace MQTTnet.Server
|
|||||||
StopInternal();
|
StopInternal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopInternal()
|
void StopInternal()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -367,162 +574,12 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket)
|
|
||||||
{
|
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
|
|
||||||
{
|
|
||||||
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
|
|
||||||
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
|
|
||||||
|
|
||||||
if (subscribeResult.CloseConnection)
|
|
||||||
{
|
|
||||||
StopInternal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscribeResult.RetainedMessages != null)
|
|
||||||
{
|
|
||||||
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages)
|
|
||||||
{
|
|
||||||
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage);
|
|
||||||
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var unsubscribeResult = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
|
|
||||||
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
|
|
||||||
|
|
||||||
if (unsubscribeResult.CloseConnection)
|
|
||||||
{
|
|
||||||
StopInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
HandleTopicAlias(publishPacket);
|
|
||||||
|
|
||||||
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
|
|
||||||
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
|
||||||
var closeConnection = false;
|
|
||||||
var processPublish = true;
|
|
||||||
|
|
||||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
|
||||||
{
|
|
||||||
interceptingPublishEventArgs = new InterceptingPublishEventArgs
|
|
||||||
{
|
|
||||||
ClientId = Id,
|
|
||||||
ApplicationMessage = applicationMessage,
|
|
||||||
SessionItems = Session.Items,
|
|
||||||
ProcessPublish = true,
|
|
||||||
CloseConnection = false,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
|
||||||
{
|
|
||||||
// This can happen if a topic alias us used but the topic is
|
|
||||||
// unknown to the server.
|
|
||||||
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
|
||||||
interceptingPublishEventArgs.ProcessPublish = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
|
||||||
|
|
||||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
|
||||||
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
|
||||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeConnection)
|
|
||||||
{
|
|
||||||
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processPublish && applicationMessage != null)
|
|
||||||
{
|
|
||||||
await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (publishPacket.QualityOfServiceLevel)
|
|
||||||
{
|
|
||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
|
||||||
{
|
|
||||||
// Do nothing since QoS 0 has no ack at all!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
|
||||||
{
|
|
||||||
var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
|
||||||
{
|
|
||||||
var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleTopicAlias(MqttPublishPacket publishPacket)
|
|
||||||
{
|
|
||||||
if (publishPacket.TopicAlias == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_topicAlias)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(publishPacket.Topic))
|
|
||||||
{
|
|
||||||
_topicAlias[publishPacket.TopicAlias] = publishPacket.Topic;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic))
|
|
||||||
{
|
|
||||||
publishPacket.Topic = topic;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
|
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
||||||
{
|
{
|
||||||
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace MQTTnet.Server
|
|||||||
readonly Dictionary<string, MqttClient> _clients = new Dictionary<string, MqttClient>(4096);
|
readonly Dictionary<string, MqttClient> _clients = new Dictionary<string, MqttClient>(4096);
|
||||||
|
|
||||||
readonly AsyncLock _createConnectionSyncRoot = new AsyncLock();
|
readonly AsyncLock _createConnectionSyncRoot = new AsyncLock();
|
||||||
|
|
||||||
readonly MqttServerEventContainer _eventContainer;
|
readonly MqttServerEventContainer _eventContainer;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttServerOptions _options;
|
readonly MqttServerOptions _options;
|
||||||
@@ -75,6 +76,8 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public async Task DeleteSessionAsync(string clientId)
|
public async Task DeleteSessionAsync(string clientId)
|
||||||
{
|
{
|
||||||
|
_logger.Verbose("Deleting session for client '{0}'.", clientId);
|
||||||
|
|
||||||
MqttClient connection;
|
MqttClient connection;
|
||||||
|
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
@@ -109,13 +112,9 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_eventContainer.SessionDeletedEvent.HasHandlers)
|
if (_eventContainer.SessionDeletedEvent.HasHandlers && session != null)
|
||||||
{
|
{
|
||||||
var eventArgs = new SessionDeletedEventArgs
|
var eventArgs = new SessionDeletedEventArgs(clientId, session.Items);
|
||||||
{
|
|
||||||
Id = session?.Id
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +196,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_createConnectionSyncRoot?.Dispose();
|
_createConnectionSyncRoot.Dispose();
|
||||||
|
|
||||||
lock (_sessionsManagementLock)
|
lock (_sessionsManagementLock)
|
||||||
{
|
{
|
||||||
@@ -295,13 +294,12 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientConnectedEventArgs
|
var eventArgs = new ClientConnectedEventArgs(
|
||||||
{
|
connectPacket.ClientId,
|
||||||
ClientId = connectPacket.ClientId,
|
connectPacket.Username,
|
||||||
UserName = connectPacket.Username,
|
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
||||||
ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
channelAdapter.Endpoint,
|
||||||
Endpoint = channelAdapter.Endpoint
|
client.Session.Items);
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -340,12 +338,8 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
|
if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientDisconnectedEventArgs
|
var disconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean;
|
||||||
{
|
var eventArgs = new ClientDisconnectedEventArgs(client.Id, disconnectType, endpoint, client.Session.Items);
|
||||||
ClientId = client.Id,
|
|
||||||
DisconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean,
|
|
||||||
Endpoint = endpoint
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -447,13 +441,18 @@ namespace MQTTnet.Server
|
|||||||
return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket, CancellationToken.None);
|
return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MqttClient CreateClient(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session)
|
||||||
|
{
|
||||||
|
return new MqttClient(connectPacket, channelAdapter, session, _options, _eventContainer, this, _rootLogger);
|
||||||
|
}
|
||||||
|
|
||||||
async Task<MqttClient> CreateClientConnection(
|
async Task<MqttClient> CreateClientConnection(
|
||||||
MqttConnectPacket connectPacket,
|
MqttConnectPacket connectPacket,
|
||||||
MqttConnAckPacket connAckPacket,
|
MqttConnAckPacket connAckPacket,
|
||||||
IMqttChannelAdapter channelAdapter,
|
IMqttChannelAdapter channelAdapter,
|
||||||
ValidatingConnectionEventArgs validatingConnectionEventArgs)
|
ValidatingConnectionEventArgs validatingConnectionEventArgs)
|
||||||
{
|
{
|
||||||
MqttClient connection;
|
MqttClient client;
|
||||||
|
|
||||||
bool sessionShouldPersist;
|
bool sessionShouldPersist;
|
||||||
|
|
||||||
@@ -493,7 +492,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (connectPacket.CleanSession)
|
if (connectPacket.CleanSession)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId);
|
||||||
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
|
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -516,46 +515,39 @@ namespace MQTTnet.Server
|
|||||||
await _eventContainer.PreparingSessionEvent.InvokeAsync(preparingSessionEventArgs).ConfigureAwait(false);
|
await _eventContainer.PreparingSessionEvent.InvokeAsync(preparingSessionEventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttClient existing;
|
MqttClient existingClient;
|
||||||
|
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
{
|
{
|
||||||
_clients.TryGetValue(connectPacket.ClientId, out existing);
|
_clients.TryGetValue(connectPacket.ClientId, out existingClient);
|
||||||
connection = CreateConnection(connectPacket, channelAdapter, session);
|
client = CreateClient(connectPacket, channelAdapter, session);
|
||||||
|
|
||||||
_clients[connectPacket.ClientId] = connection;
|
_clients[connectPacket.ClientId] = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing != null)
|
if (existingClient != null)
|
||||||
{
|
{
|
||||||
existing.IsTakenOver = true;
|
existingClient.IsTakenOver = true;
|
||||||
await existing.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
|
await existingClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
|
||||||
|
|
||||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientDisconnectedEventArgs
|
var eventArgs = new ClientDisconnectedEventArgs(
|
||||||
{
|
existingClient.Id,
|
||||||
ClientId = existing.Id,
|
MqttClientDisconnectType.Takeover,
|
||||||
DisconnectType = MqttClientDisconnectType.Takeover,
|
existingClient.Endpoint,
|
||||||
Endpoint = existing.Endpoint
|
existingClient.Session.Items);
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection;
|
return client;
|
||||||
}
|
|
||||||
|
|
||||||
MqttClient CreateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session)
|
|
||||||
{
|
|
||||||
return new MqttClient(connectPacket, channelAdapter, session, _options, _eventContainer, this, _rootLogger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent)
|
MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Created a new session for client '{0}'.", clientId);
|
_logger.Verbose("Created new session for client '{0}'.", clientId);
|
||||||
|
|
||||||
return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this);
|
return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this);
|
||||||
}
|
}
|
||||||
@@ -572,12 +564,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventArgs = new ApplicationMessageNotConsumedEventArgs
|
var eventArgs = new ApplicationMessageNotConsumedEventArgs(applicationMessage, senderId);
|
||||||
{
|
|
||||||
ApplicationMessage = applicationMessage,
|
|
||||||
SenderId = senderId
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,25 +610,25 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
||||||
{
|
{
|
||||||
var context = new ValidatingConnectionEventArgs(connectPacket, channelAdapter)
|
var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter)
|
||||||
{
|
{
|
||||||
SessionItems = new ConcurrentDictionary<object, object>()
|
SessionItems = new ConcurrentDictionary<object, object>()
|
||||||
};
|
};
|
||||||
|
|
||||||
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(context).ConfigureAwait(false);
|
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
// Check the client ID and set a random one if supported.
|
// Check the client ID and set a random one if supported.
|
||||||
if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
||||||
{
|
{
|
||||||
connectPacket.ClientId = context.AssignedClientIdentifier;
|
connectPacket.ClientId = eventArgs.AssignedClientIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(connectPacket.ClientId))
|
if (string.IsNullOrEmpty(connectPacket.ClientId))
|
||||||
{
|
{
|
||||||
context.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
eventArgs.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return eventArgs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,12 +217,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
foreach (var finalTopicFilter in finalTopicFilters)
|
foreach (var finalTopicFilter in finalTopicFilters)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientSubscribedTopicEventArgs
|
var eventArgs = new ClientSubscribedTopicEventArgs(_session.Id, finalTopicFilter, _session.Items);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
TopicFilter = finalTopicFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,12 +307,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientUnsubscribedTopicEventArgs
|
var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, topicFilter, _session.Items);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
TopicFilter = topicFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -472,14 +462,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<InterceptingSubscriptionEventArgs> InterceptSubscribe(MqttTopicFilter topicFilter, CancellationToken cancellationToken)
|
async Task<InterceptingSubscriptionEventArgs> InterceptSubscribe(MqttTopicFilter topicFilter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var eventArgs = new InterceptingSubscriptionEventArgs
|
var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, new MqttSessionStatus(_session), topicFilter);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
TopicFilter = topicFilter,
|
|
||||||
SessionItems = _session.Items,
|
|
||||||
Session = new MqttSessionStatus(_session),
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
@@ -508,13 +491,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
|
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs
|
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, topicFilter, _session.Items, topicFilter);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
Topic = topicFilter,
|
|
||||||
SessionItems = _session.Items,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mqttSubscription == null)
|
if (mqttSubscription == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -105,13 +105,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var eventArgs = new RetainedMessageChangedEventArgs
|
var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave);
|
||||||
{
|
|
||||||
ClientId = clientId,
|
|
||||||
ChangedRetainedMessage = applicationMessage,
|
|
||||||
StoredRetainedMessages = messagesForSave
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,40 +9,42 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class MqttServerEventContainer
|
public sealed class MqttServerEventContainer
|
||||||
{
|
{
|
||||||
public AsyncEvent<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
|
public AsyncEvent<ApplicationMessageNotConsumedEventArgs> ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent<ApplicationMessageNotConsumedEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
|
public AsyncEvent<ClientAcknowledgedPublishPacketEventArgs> ClientAcknowledgedPublishPacketEvent { get; } = new AsyncEvent<ClientAcknowledgedPublishPacketEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientConnectedEventArgs> ClientConnectedEvent { get; } = new AsyncEvent<ClientConnectedEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<InterceptingPacketEventArgs> InterceptingOutboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPublishEventArgs> InterceptingPublishEvent { get; } = new AsyncEvent<InterceptingPublishEventArgs>();
|
public AsyncEvent<InterceptingPublishEventArgs> InterceptingPublishEvent { get; } = new AsyncEvent<InterceptingPublishEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
|
public AsyncEvent<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ClientConnectedEventArgs> ClientConnectedEvent { get; } = new AsyncEvent<ClientConnectedEventArgs>();
|
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> PreparingSessionEvent { get; } = new AsyncEvent<EventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<SessionDeletedEventArgs> SessionDeletedEvent { get; } = new AsyncEvent<SessionDeletedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<ApplicationMessageNotConsumedEventArgs> ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent<ApplicationMessageNotConsumedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<RetainedMessageChangedEventArgs> RetainedMessageChangedEvent { get; } = new AsyncEvent<RetainedMessageChangedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<LoadingRetainedMessagesEventArgs> LoadingRetainedMessagesEvent { get; } = new AsyncEvent<LoadingRetainedMessagesEventArgs>();
|
public AsyncEvent<LoadingRetainedMessagesEventArgs> LoadingRetainedMessagesEvent { get; } = new AsyncEvent<LoadingRetainedMessagesEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<EventArgs> PreparingSessionEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<RetainedMessageChangedEventArgs> RetainedMessageChangedEvent { get; } = new AsyncEvent<RetainedMessageChangedEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> RetainedMessagesClearedEvent { get; } = new AsyncEvent<EventArgs>();
|
public AsyncEvent<EventArgs> RetainedMessagesClearedEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
public AsyncEvent<SessionDeletedEventArgs> SessionDeletedEvent { get; } = new AsyncEvent<SessionDeletedEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPacketEventArgs> InterceptingOutboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> StartedEvent { get; } = new AsyncEvent<EventArgs>();
|
public AsyncEvent<EventArgs> StartedEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> StoppedEvent { get; } = new AsyncEvent<EventArgs>();
|
public AsyncEvent<EventArgs> StoppedEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,9 +71,18 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public bool WillMessageSent { get; set; }
|
public bool WillMessageSent { get; set; }
|
||||||
|
|
||||||
public void AcknowledgePublishPacket(ushort packetIdentifier)
|
public MqttPublishPacket PeekAcknowledgePublishPacket(ushort packetIdentifier)
|
||||||
{
|
{
|
||||||
_unacknowledgedPublishPackets.TryRemove(packetIdentifier, out _);
|
// This will only return the matching PUBLISH packet but does not remove it.
|
||||||
|
// This is required for QoS 2.
|
||||||
|
_unacknowledgedPublishPackets.TryGetValue(packetIdentifier, out var publishPacket);
|
||||||
|
return publishPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttPublishPacket AcknowledgePublishPacket(ushort packetIdentifier)
|
||||||
|
{
|
||||||
|
_unacknowledgedPublishPackets.TryRemove(packetIdentifier, out var publishPacket);
|
||||||
|
return publishPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSubscribedTopic(string topic)
|
public void AddSubscribedTopic(string topic)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,17 +21,18 @@ namespace MQTTnet.Server
|
|||||||
public class MqttServer : Disposable
|
public class MqttServer : Disposable
|
||||||
{
|
{
|
||||||
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
||||||
|
|
||||||
|
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
|
||||||
readonly ICollection<IMqttServerAdapter> _adapters;
|
readonly ICollection<IMqttServerAdapter> _adapters;
|
||||||
|
readonly MqttClientSessionsManager _clientSessionsManager;
|
||||||
|
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttServerOptions _options;
|
readonly MqttServerOptions _options;
|
||||||
readonly IMqttNetLogger _rootLogger;
|
|
||||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
readonly MqttClientSessionsManager _clientSessionsManager;
|
|
||||||
|
|
||||||
CancellationTokenSource _cancellationTokenSource;
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
@@ -55,6 +58,12 @@ namespace MQTTnet.Server
|
|||||||
remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value);
|
remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Func<ClientAcknowledgedPublishPacketEventArgs, Task> ClientAcknowledgedPublishPacketAsync
|
||||||
|
{
|
||||||
|
add => _eventContainer.ClientAcknowledgedPublishPacketEvent.AddHandler(value);
|
||||||
|
remove => _eventContainer.ClientAcknowledgedPublishPacketEvent.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
public event Func<ClientConnectedEventArgs, Task> ClientConnectedAsync
|
public event Func<ClientConnectedEventArgs, Task> ClientConnectedAsync
|
||||||
{
|
{
|
||||||
add => _eventContainer.ClientConnectedEvent.AddHandler(value);
|
add => _eventContainer.ClientConnectedEvent.AddHandler(value);
|
||||||
@@ -158,7 +167,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsStarted => _cancellationTokenSource != null;
|
public bool IsStarted => _cancellationTokenSource != null;
|
||||||
|
|
||||||
public Task DeleteRetainedMessagesAsync()
|
public Task DeleteRetainedMessagesAsync()
|
||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
@@ -174,10 +183,10 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
|
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<MqttClientStatus>> GetClientsAsync()
|
public Task<IList<MqttClientStatus>> GetClientsAsync()
|
||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
@@ -199,7 +208,7 @@ namespace MQTTnet.Server
|
|||||||
return _clientSessionsManager.GetSessionStatusAsync();
|
return _clientSessionsManager.GetSessionStatusAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
|
public async Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
|
||||||
{
|
{
|
||||||
if (injectedApplicationMessage == null)
|
if (injectedApplicationMessage == null)
|
||||||
{
|
{
|
||||||
@@ -215,7 +224,34 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, injectedApplicationMessage.ApplicationMessage);
|
var processPublish = true;
|
||||||
|
var applicationMessage = injectedApplicationMessage.ApplicationMessage;
|
||||||
|
|
||||||
|
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(
|
||||||
|
applicationMessage,
|
||||||
|
_cancellationTokenSource.Token,
|
||||||
|
injectedApplicationMessage.SenderClientId,
|
||||||
|
_sessionItems);
|
||||||
|
|
||||||
|
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
|
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||||
|
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!processPublish)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(applicationMessage.Topic))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Injected application messages must contain a topic. Topic alias is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, applicationMessage).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync()
|
public async Task StartAsync()
|
||||||
@@ -236,7 +272,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Info("Started.");
|
_logger.Info("Started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +302,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Info("Stopped.");
|
_logger.Info("Stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +346,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
|
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
|
||||||
{
|
{
|
||||||
if (retainedMessage == null)
|
if (retainedMessage == null)
|
||||||
@@ -347,7 +383,7 @@ namespace MQTTnet.Server
|
|||||||
void ThrowIfNotStarted()
|
void ThrowIfNotStarted()
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
if (_cancellationTokenSource == null)
|
if (_cancellationTokenSource == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The MQTT server is not started.");
|
throw new InvalidOperationException("The MQTT server is not started.");
|
||||||
@@ -357,7 +393,7 @@ namespace MQTTnet.Server
|
|||||||
void ThrowIfStarted()
|
void ThrowIfStarted()
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
if (_cancellationTokenSource != null)
|
if (_cancellationTokenSource != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The MQTT server is already started.");
|
throw new InvalidOperationException("The MQTT server is already started.");
|
||||||
|
|||||||
Reference in New Issue
Block a user