Compare commits

..
76 Commits
Author SHA1 Message Date
Christian f74af45fba Add new samples. 2022-10-01 11:32:14 +02:00
Christian e050e2d61e Improve async lock (#1520) 2022-09-25 10:26:18 +02:00
Christian ef85ef676a Update README.md 2022-09-16 08:24:52 +02:00
Christian e977546297 Fix cross access to async events. (#1515)
fix cross access to async events.
2022-09-16 08:20:42 +02:00
Rido ae92b0ed33 Add Extensions.MultiCloud to references (#1517) 2022-09-15 09:21:39 +02:00
Christian 7e686d9aa5 Refactor code. 2022-09-15 09:09:08 +02:00
Christian d23bbf7bf3 Improve PingAsync timeout handling. 2022-09-14 14:17:59 +02:00
Christian d4dad4860a Fix and improve session takeover process handling. 2022-09-14 14:15:35 +02:00
Christian c009cbef76 Update comments, release notes, version. 2022-09-13 09:07:02 +02:00
logicaloud 37e60c73ae Send DISCONNECT packets to MQTT5 clients only (#1506) 2022-09-13 09:00:21 +02:00
Christian 3ab095a568 Set web socket boundaries correctly (#1514) 2022-09-13 08:50:02 +02:00
Christian f5d9b65139 Add samples. 2022-08-23 20:58:46 +02:00
Christian 31e5b40459 Revert "Update ci.yml"
This reverts commit 8fb55d75fe.
2022-08-23 20:49:40 +02:00
Christian 8fb55d75fe Update ci.yml 2022-08-14 13:06:32 +02:00
Christian 420222511b Update ReleaseNotes.md 2022-08-13 12:56:05 +02:00
Stan WijckmansandStan Wijckmans c5cec60809 Apply timeout to automatic ping. (#1495)
Co-authored-by: Stan Wijckmans <stanw@aviobook.aero>
2022-08-13 12:53:55 +02:00
Christian 12dd815421 Fix wrong package references in Benchmark project. 2022-08-13 12:45:02 +02:00
Christian f36f49cb3e Add parameter validations. 2022-08-13 12:38:36 +02:00
Christian 9152fe637e Reset release notes. 2022-08-07 09:56:25 +02:00
Christian b976e9c177 Fix build errors. 2022-08-03 20:44:58 +02:00
Christian 262a4681ad Merge branch '1291-access-client-status-in-client-disconnected-handler' 2022-08-03 20:40:53 +02:00
Christian ad546adafe Fix removal of event handlers from async event. 2022-08-03 20:27:53 +02:00
Christian 29a9941d86 Fix breaking changes. 2022-07-31 11:10:38 +02:00
Christian 5a21939e29 Squashed commit of the following:
commit 469caf656e
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sun Jul 31 10:55:40 2022 +0200

    Fix build errors.

commit 42402fba04
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sun Jul 31 10:22:23 2022 +0200

    Allow interception of injected application messages.

commit a0dc8ab0d7
Author: William Lee <31092948+william19821101@users.noreply.github.com>
Date:   Sun Jul 31 15:50:14 2022 +0800

    Update Server_Simple_Samples.cs (#1474)

    Change e.UserName to e.Username
2022-07-31 11:09:39 +02:00
Christian 469caf656e Fix build errors. 2022-07-31 10:55:40 +02:00
Christian 42402fba04 Allow interception of injected application messages. 2022-07-31 10:22:23 +02:00
William Lee a0dc8ab0d7 Update Server_Simple_Samples.cs (#1474)
Change e.UserName to e.Username
2022-07-31 09:50:14 +02:00
Christian 02ede5a2d8 Update MqttClient.cs 2022-07-24 10:44:27 +02:00
Christian cef61900df Merge branch 'master' into 1291-access-client-status-in-client-disconnected-handler 2022-07-23 18:09:05 +02:00
Christian 09385b1b36 Pass session item to several more event handers. 2022-07-23 18:07:49 +02:00
Christian cbf413f04e Squashed commit of the following: 1125-expose-http-context-in-validateconnectionasync
commit 353c9671a04a273798b0b3775be5f0764bfd7edf
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:33:01 2022 +0200

    Remove usage of obsolete stuff.

commit 1f3c35685c7a65453c37a37e8c54a2ca4305ff6d
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:30:54 2022 +0200

    Update ValidatingConnectionEventArgs.cs

commit a5e1b8938974b5b57e95ec41d74634ca130a8ad5
Merge: 8878efee 11249d00
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:30:30 2022 +0200

    Merge branch 'master' into 1125-expose-http-context-in-validateconnectionasync

commit 8878efee0da3c67d90bb7fb6946453f62329cd8e
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:30:03 2022 +0200

    Expose channel adapter when validating connections.
2022-07-23 15:33:53 +02:00
Christian 11249d006f Add samples. 2022-07-23 15:10:20 +02:00
Christian 0d7b020994 Squashed commit of the following: 487-any-way-to-generate-events-on-receipt-of-puback-or-pubrec-packets
commit 3f2f8d4b0a31b20734eca3fff86c43bd1ac3e3ad
Merge: 34babbf2 d71ff3d6
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 11:06:22 2022 +0200

    Merge branch 'master' into 487-any-way-to-generate-events-on-receipt-of-puback-or-pubrec-packets

commit 34babbf2714b742bc4f3802804a1a07159d38e22
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Mon Jul 11 22:56:01 2022 +0200

    Fix typo.

commit add6277458f528fc03bf4a1ba228a55c5a8d1a80
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 2 12:50:13 2022 +0200

    Add new event for client acknowledges.
2022-07-23 11:08:53 +02:00
Christian d71ff3d615 Update ReleaseNotes.md 2022-07-23 11:05:14 +02:00
Christian 57745e8324 Merge branch 'master' into 1311-how-does-mqttclient-specify-the-network-card-to-send-data 2022-07-23 11:03:06 +02:00
Christian a94776eeea Update version information. 2022-07-23 11:02:00 +02:00
Christian d3848d8cb0 Add comments. 2022-07-23 11:00:21 +02:00
Alexander Köpke 6c29ea56fc Add PackageIdentifier to MqttApplicationMessageReceivedEventArgs (#1466)
Provide `PackageIdentifier` as read only property in `MqttApplicationMessageReceivedEventArgs`
2022-07-23 10:37:57 +02:00
Christian ed7c150cc5 Add new options. 2022-07-19 20:47:16 +02:00
Christian 13762eedd4 Update Server_Asp_Net_Samples.cs 2022-07-19 17:33:01 +02:00
Christian e33495010e Update samples. 2022-07-13 15:29:26 +02:00
Christian b0d1fcd161 Refactor code. 2022-07-12 16:37:32 +02:00
Christian f7a739e162 Fix check for skipped application messages. 2022-07-12 12:02:00 +02:00
Christian 422945929e Merge branch 'Fix-high-CPU-usage' 2022-07-12 11:57:03 +02:00
Christian 76e54dc49b Update ReleaseNotes.md 2022-07-12 11:56:50 +02:00
Christian b10a69a0ae Update release notes. 2022-07-01 17:03:33 +02:00
Torstein Lundervold Nesheim 4b1c7cf772 Use IgnoreCertificateChainErrors option in TcpChannel (#1447) 2022-07-01 17:00:13 +02:00
Christian 3f67df007b Remove obsolete files etc. 2022-07-01 16:55:13 +02:00
Christian 36b1ed06f0 Update MqttClient.cs 2022-07-01 16:54:53 +02:00
Christian 4a982ea228 Add Tests. 2022-06-29 18:58:02 +02:00
Christian 9fe5073186 Use TopicFilter instead of QoS only. 2022-06-29 18:17:48 +02:00
Christian 305c552474 Squashed commit of the following:
commit 0cbf50427ca6fb343cfa22ccbcd06aad5ea2b4c2
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Mon Jun 20 21:02:50 2022 +0200

    Fix firing of _ClientSubscribedTopicAsync_ event.
2022-06-27 20:41:34 +02:00
Christian f632fbb447 Squashed commit of the following:
commit c3ca0ef7da4bcc2103cda2bb175ea3c5a49da2e6
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jun 25 22:58:18 2022 +0200

    Update nugets and fix breaking changes.
2022-06-27 20:40:37 +02:00
Christian 8231d62f9f Squashed commit of the following:
commit 9f289152736a682a8c31fbe7720dade37dbb1cb5
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sun Jun 26 13:38:35 2022 +0200

    Fix message storage.
2022-06-27 20:39:53 +02:00
Christian a52a717965 Update ReleaseNotes.md 2022-06-26 13:40:21 +02:00
Christian 2b653f2379 Update ReleaseNotes.md 2022-06-25 23:00:16 +02:00
Christian 725cfb643f Set MaximumQoS properly. 2022-06-24 18:52:50 +02:00
Christian 29ec6edbf4 Update Server_Simple_Samples.cs 2022-06-24 18:46:38 +02:00
Christian f3ef2dbf7c Add samples. 2022-06-18 11:56:19 +02:00
Christian 14eb202873 Add new samples. 2022-06-18 11:38:36 +02:00
Christian a3c7f9a22c Fix handling of retained messages with QoS > 0. 2022-06-15 21:56:13 +02:00
Christian 41e0a25fc8 Fix wrong event args types. 2022-06-14 21:10:53 +02:00
Christian 772cd3cd12 Update release notes. 2022-06-10 23:29:52 +02:00
Christian 42cf9f73c2 Merge branch 'master' into 1413-protocol-version-132-mqtt-311-bridge-support 2022-06-10 23:27:00 +02:00
Christian ec2e08668f Add support for MQTT 3.1.1 Bridge flag. 2022-06-10 23:07:00 +02:00
Christian e9e8583d1a Merge branch 'master' of https://github.com/dotnet/MQTTnet 2022-06-10 17:10:14 +02:00
Christian f68b109471 Switch to license expression. (#1408) 2022-06-10 17:09:17 +02:00
Christian dd1e155ff4 Add missing "WithWill..." methods. 2022-06-09 21:52:05 +02:00
Christian cf6369d63f Update version. 2022-06-09 21:27:14 +02:00
Christian 3049fc6608 Update ReleaseNotes.md 2022-06-09 21:27:08 +02:00
Christian b75076a1c5 Skip adding default message expiry interval. 2022-06-09 21:23:34 +02:00
Christian ae8e2ec7c9 Merge branch 'master' of https://github.com/dotnet/MQTTnet 2022-06-09 20:13:19 +02:00
Christian e7e6a080e0 Update copyright information. 2022-06-09 20:13:17 +02:00
SeppPenner dc9afa6c3a Samples, Github template: Fixed small typos. 2022-06-08 09:19:14 +02:00
HansM 02b17f277d Merge pull request #1421 from soyoo/patch-1
Update RcpClient_Samples.cs
2022-06-08 08:58:22 +02:00
soyoo b9d9ea2d4d Update RcpClient_Samples.cs
typo
2022-06-07 21:54:08 +08:00
109 changed files with 3256 additions and 1666 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ A clear and concise description of what the bug is.
<!-- Remove the items which don't apply from the following list. -->
- Client
- ManagedClient
- RcpClient
- RpcClient
- Server
### To Reproduce
-1
View File
@@ -14,6 +14,5 @@ A clear and concise description of what you want to know.
<!-- Remove the items which don't apply from the following list -->
- Client
- ManagedClient
- MQTTnet.Server standalone
- Server
- Generic
@@ -15,7 +15,6 @@ A clear and concise description of what the problem is. Example. I'm am trying t
<!-- Remove the items which don't apply from the following list -->
- Client
- ManagedClient
- MQTTnet.Server standalone
- Server
- Generic
+7 -32
View File
@@ -1,32 +1,7 @@
We have joined the .NET Foundation!
Version 4 comes with a new API so a lot of breaking changes should be expected.
Checkout the upgrade guide (https://github.com/dotnet/MQTTnet/wiki/Upgrading-guide) for an overview of the changes.
Checkout the new samples (https://github.com/dotnet/MQTTnet/tree/feature/master/Samples) how to use the new API. The wiki only remains for version 3 of this library.
Preview builds of this library are available at: https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet
* [Core] Improved memory management when working with large payloads.
* [Core] Added support for .NET 6.0.
* [Core] nuget packages are now created by MSBuild including more information (i.e. commit hash).
* [Client] Exposed socket linger state in options.
* [Client] The OS will now choose the best TLS version to use. It is no longer fixed to 1.3 etc. (thanks to @patagonaa, #1271).
* [Client] Added support for _ServerKeepAlive_ (MQTTv5).
* [Client] Exposed user properties and reason string in subscribe result.
* [Client] Exposed user properties and reason string in unsubscribe result.
* [Client] Migrated application message handler to a regular .NET event (BREAKING CHANGE!).
* [Client] The will message is longer a regular application message due to not supported properties by the will message (BREAKING CHANGE!).
* [Client] Timeouts are no longer handled inside the library. Each method (Connect, Publish etc.) supports a cancellation token so that custom timeouts can and must be used (BREAKING CHANGE!).
* [Client] Exposed certificate revocation mode on options (@andyolivares).
* [Server] Exposed socket linger state in options.
* [Server] Added support for returning individual subscription errors (#80 thanks to @jimch)
* [Server] Improved topic filter comparisons (support for $).
* [Server] Added more MQTTv5 response information to all interceptors (BREAKING CHANGE!).
* [Server] Improved session management for MQTT v5 (#1294, thanks to @logicaloud).
* [Server] All interceptors and events are migrated from interfaces to simple events. All existing APIs are availble but must be migrated to corresponding events (BREAKING CHANGE!).
* [Server] Removed all interceptor and event interfaces including the delegate implementations etc. (BREAKING CHANGE!).
* [Server] Renamed a lot of classes and adjusted namespaces (BREAKING CHANGE!).
* [Server] Introduced a new queueing approach for internal message process (packet bus).
* [Server] For security reasons the default endpoint (1883) is no longer enabled by default (BREAKING CHANGE!).
* [Server] Added support for choosing the cipher suite (thanks to @TimSiefert).
* [Nuget] Added method summaries etc. to nuget packages (thanks to @SpringHgui).
* [Core] MQTT Packets being sent over web socket transport are now setting the web socket frame boundaries correctly (#1499).
* [Core] Add support for attaching and detaching events from different threads.
* [Core] Fixed a deadlock in _AsyncLock_ implementation (#1520).
* [Client] Keep alive mechanism now uses the configured timeout value from the options (thanks to @Stannieman, #1495).
* [Client] The _PingAsync_ will fallback to the timeout specified in the client options when the cancellation token cannot be cancelled.
* [Server] A DISCONNECT packet is no longer sent to MQTT clients < 5.0.0 (thanks to @logicaloud, #1506).
* [Server] Improved "take over" process handling.
+2 -3
View File
@@ -3,8 +3,7 @@ name: CI
on: [push, pull_request]
env:
ASSEMBLY_VERSION: "4.0.0.${{github.run_number}}"
NUGET_VERSION: "4.0.0.${{github.run_number}}"
VERSION: "4.1.1.${{github.run_number}}"
jobs:
build:
@@ -40,7 +39,7 @@ jobs:
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
- name: Build solution
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.ASSEMBLY_VERSION }} /p:AssemblyVersion=${{ env.ASSEMBLY_VERSION }} /p:PackageVersion=${{ env.NUGET_VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
- name: Collect nuget Packages
uses: actions/upload-artifact@v2
+6 -1
View File
@@ -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/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;
&lt;TypePattern DisplayName="Non-reorderable types"&gt;
&lt;TypePattern.Match&gt;
&lt;Or&gt;
@@ -210,6 +210,7 @@
&lt;/Or&gt;
&lt;/Entry.Match&gt;
&lt;Entry.SortBy&gt;
&lt;Access Order="Public Internal Protected Private" /&gt;
&lt;Name /&gt;
&lt;/Entry.SortBy&gt;
&lt;/Entry&gt;
@@ -226,6 +227,9 @@
&lt;/Entry&gt;
&lt;/TypePattern&gt;
&lt;/Patterns&gt;</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_002ECSharpKeepExistingMigration/@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_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -34
View File
@@ -12,7 +12,7 @@
# MQTTnet
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5.
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports the MQTT protocol up to version 5. It is compatible with mostly any supported .NET Framework version and CPU architecture.
## Features
@@ -50,39 +50,6 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Supported frameworks
| Framwork | Version |
| ------------------ | ----------- |
|.NET | 5.0+ |
|.NET Framework | 4.5.2+ |
|.NET Standard | 1.3+ |
|.NET Core | 1.1+ |
|.NET Core App | 1.1+ |
| Mono | 5.2+ |
| UWP | 10.0.10240+ |
| Xamarin.Android | 7.5+ |
| Xamarin.iOS | 10.14+ |
| Blazor WebAssembly | 3.2.0+ |
## References
This library is used in the following projects:
* Azure Functions MQTT Bindings, <https://github.com/keesschollaart81/CaseOnline.Azure.WebJobs.Extensions.Mqtt>)
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
* MQTT Client Rx (Managed Client Wrapper for Reactive Extensions, <https://github.com/mmuecke/RxMQTTnet>)
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
* MQTTnet App (Cross platform client application for MQTT debugging, inspection etc., <https://github.com/chkr1011/MQTTnetApp>)
* Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
* SparkplugNet (Sparkplug library for .Net, <https://github.com/SeppPenner/SparkplugNet>)
* Silverback (Framework to build event-driven applications - support for MQTT, Kafka & RabbitMQ) <https://github.com/BEagle1984/silverback>
Further projects using this project can be found under https://github.com/dotnet/MQTTnet/network/dependents.
If you use this library and want to see your project here please create a pull request.
## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+86 -11
View File
@@ -2,8 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System.Security.Authentication;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
using MQTTnet.Samples.Helpers;
@@ -111,13 +116,21 @@ public static class Client_Connection_Samples
.WithTls(
o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true;
// The default value is determined by the OS. Set manually to force version.
o.SslProtocol = SslProtocols.Tls12;
})
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
using (var timeout = new CancellationTokenSource(5000))
{
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected.");
Console.WriteLine("The MQTT client is connected.");
}
}
}
@@ -142,6 +155,30 @@ public static class Client_Connection_Samples
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()
{
@@ -159,19 +196,59 @@ public static class Client_Connection_Samples
.WithTls(
o =>
{
o.SslProtocol = SslProtocols.Tls12; // The default value is determined by the OS. Set manually to force version.
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true;
})
.Build();
// In MQTTv5 the response contains much more information.
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
using (var timeout = new CancellationTokenSource(5000))
{
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected.");
Console.WriteLine("The MQTT client is connected.");
response.DumpToConsole();
response.DumpToConsole();
}
}
}
public static async Task Inspect_Certificate_Validation_Errors()
{
/*
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
* including the reason for that status.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTls(
o =>
{
o.CertificateValidationHandler = eventArgs =>
{
eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole();
return true;
};
})
.Build();
// In MQTTv5 the response contains much more information.
using (var timeout = new CancellationTokenSource(5000))
{
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
}
}
}
public static async Task Ping_Server()
{
/*
@@ -244,14 +321,12 @@ public static class Client_Connection_Samples
{
try
{
// This code will also do the very first connect! So no call to _ConnectAsync_ is required
// in the first place.
if (!mqttClient.IsConnected)
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
if (!await mqttClient.TryPingAsync())
{
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Subscribe to topics when session is clean etc.
Console.WriteLine("The MQTT client is connected.");
}
}
+51
View File
@@ -2,6 +2,10 @@
// 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;
namespace MQTTnet.Samples.Client;
@@ -36,6 +40,53 @@ public static class Client_Publish_Samples
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
}
public static async Task Publish_Multiple_Application_Messages()
{
/*
* This sample pushes multiple simple application message including a topic and a payload.
*
* See sample _Publish_Application_Message_ for more details.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("20.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("21.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
}
@@ -2,7 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Client;
@@ -77,4 +82,48 @@ public static class Client_Subscribe_Samples
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();
}
}
}
+4
View File
@@ -2,6 +2,10 @@
// 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;
using MQTTnet.Diagnostics;
@@ -2,6 +2,10 @@
// 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.Diagnostics;
namespace MQTTnet.Samples.Diagnostics;
+1 -2
View File
@@ -15,8 +15,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
@@ -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.Extensions.ManagedClient;
+9 -3
View File
@@ -39,10 +39,16 @@ Console.WriteLine();
try
{
var task = selectedSampleMethod.Invoke(null, null) as Task;
task?.Wait();
if (selectedSampleMethod.Invoke(null, null) is Task task)
{
await task;
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
Console.WriteLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
@@ -2,13 +2,17 @@
// 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.Extensions.Rpc;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.RpcClient;
public static class RcpClient_Samples
public static class RpcClient_Samples
{
/*
* The extension MQTTnet.Extensions.Rpc (available as nuget) allows sending a request and waiting for the matching reply.
@@ -33,7 +37,7 @@ public static class RcpClient_Samples
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
{
// Access to a fully featured application message is not supported for RCP calls!
// Access to a fully featured application message is not supported for RPC calls!
// The method will throw an exception when the response was not received in time.
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
@@ -81,4 +85,4 @@ public static class RcpClient_Samples
// ! Do not send response message with the _retain_ flag set to true.
// ! All required data for a RPC call and the result must be placed into the payload.
*/
}
}
+110
View File
@@ -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>();
}
}
}
@@ -0,0 +1,82 @@
// 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;
namespace MQTTnet.Samples.Server;
public static class Server_Retained_Messages_Samples
{
public static async Task Persist_Retained_Messages()
{
/*
* This sample starts a simple MQTT server which will store all retained messages in a file.
*/
var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json");
var mqttFactory = new MqttFactory();
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var server = mqttFactory.CreateMqttServer(mqttServerOptions))
{
// Make sure that the server will load the retained messages.
server.LoadingRetainedMessageAsync += async eventArgs =>
{
try
{
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
Console.WriteLine("Retained messages loaded.");
}
catch (FileNotFoundException)
{
// Ignore because nothing is stored yet.
Console.WriteLine("No retained messages stored yet.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to persist the changed retained messages.
server.RetainedMessageChangedAsync += async eventArgs =>
{
try
{
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to clear the retained messages when they are all deleted via API.
server.RetainedMessagesClearedAsync += _ =>
{
File.Delete(storePath);
return Task.CompletedTask;
};
await server.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
}
}
+143 -15
View File
@@ -2,6 +2,11 @@
// 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.Diagnostics;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -9,27 +14,95 @@ namespace MQTTnet.Samples.Server;
public static class Server_Simple_Samples
{
public static async Task Force_Disconnecting_Client()
{
/*
* This sample will disconnect a client.
*
* See _Run_Minimal_Server_ for more information.
*/
using (var mqttServer = await StartMqttServer())
{
// Let the client connect.
await Task.Delay(TimeSpan.FromSeconds(5));
// Now disconnect the client (if connected).
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
if (affectedClient != null)
{
await affectedClient.DisconnectAsync();
}
}
}
public static async Task Publish_Message_From_Broker()
{
/*
* This sample will publish a message directly at the broker.
*
* See _Run_Minimal_Server_ for more information.
*/
using (var mqttServer = await StartMqttServer())
{
// Create a new message using the builder as usual.
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
// Now inject the new message at the broker.
await mqttServer.InjectApplicationMessage(
new InjectedMqttApplicationMessage(message)
{
SenderClientId = "SenderClientId"
});
}
}
public static async Task Run_Minimal_Server()
{
/*
* This sample starts a simple MQTT server which will accept any TCP connection.
*/
var mqttFactory = new MqttFactory();
// The port for the default endpoint is 1883.
// The default endpoint is NOT encrypted!
// Use the builder classes where possible.
var mqttServerOptions = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.Build();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
// The port can be changed using the following API (not used in this example).
new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.WithDefaultEndpointPort(1234)
.Build();
// new MqttServerOptionsBuilder()
// .WithDefaultEndpoint()
// .WithDefaultEndpointPort(1234)
// .Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
}
public static async Task Run_Server_With_Logging()
{
/*
* This sample starts a simple MQTT server and prints the logs to the output.
*
* IMPORTANT! Do not enable logging in live environment. It will decrease performance.
*
* See sample "Run_Minimal_Server" for more details.
*/
var mqttFactory = new MqttFactory(new ConsoleLogger());
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
@@ -52,9 +125,7 @@ public static class Server_Simple_Samples
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.Build();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
@@ -67,7 +138,7 @@ public static class Server_Simple_Samples
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
}
if (e.Username != "ValidUser")
if (e.UserName != "ValidUser")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
@@ -88,4 +159,61 @@ public static class Server_Simple_Samples
await mqttServer.StopAsync();
}
}
}
static async Task<MqttServer> StartMqttServer()
{
var mqttFactory = new MqttFactory();
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
var server = mqttFactory.CreateMqttServer(mqttServerOptions);
await server.StartAsync();
return server;
}
class ConsoleLogger : IMqttNetLogger
{
readonly object _consoleSyncRoot = new();
public bool IsEnabled => true;
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
{
var foregroundColor = ConsoleColor.White;
switch (logLevel)
{
case MqttNetLogLevel.Verbose:
foregroundColor = ConsoleColor.White;
break;
case MqttNetLogLevel.Info:
foregroundColor = ConsoleColor.Green;
break;
case MqttNetLogLevel.Warning:
foregroundColor = ConsoleColor.DarkYellow;
break;
case MqttNetLogLevel.Error:
foregroundColor = ConsoleColor.Red;
break;
}
if (parameters?.Length > 0)
{
message = string.Format(message, parameters);
}
lock (_consoleSyncRoot)
{
Console.ForegroundColor = foregroundColor;
Console.WriteLine(message);
if (exception != null)
{
Console.WriteLine(exception);
}
}
}
}
}
@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
@@ -16,7 +16,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -27,7 +27,7 @@
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -41,16 +41,9 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -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))
{
}
}
}
}
}
}
@@ -46,7 +46,7 @@ namespace MQTTnet.Benchmarks
_stream.Position = 0;
var channel = new TestMqttChannel(_stream);
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
+1 -1
View File
@@ -8,7 +8,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark
@@ -12,14 +12,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
@@ -6,11 +6,10 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MessageProcessingBenchmark
@@ -13,7 +13,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark
{
@@ -7,7 +7,7 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark
{
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes;
using MQTTnet.Channel;
using MQTTnet.Implementations;
@@ -14,7 +15,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public sealed class MqttTcpChannelBenchmark
{
@@ -70,8 +71,8 @@ namespace MQTTnet.Benchmarks
while (read < expected)
{
var readresult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readresult;
var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
read += readResult;
}
}
@@ -79,9 +80,11 @@ namespace MQTTnet.Benchmarks
{
await Task.Yield();
var buffer = new ArraySegment<byte>(new byte[size]);
for (var i = 0; i < iterations; i++)
{
await _serverChannel.WriteAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false);
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
}
}
}
+4
View File
@@ -29,6 +29,7 @@ namespace MQTTnet.Benchmarks
Console.WriteLine("c = SubscribeBenchmark");
Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark");
Console.WriteLine("f = AsyncLockBenchmark");
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
@@ -75,6 +76,9 @@ namespace MQTTnet.Benchmarks
case 'e':
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
break;
case 'f':
BenchmarkRunner.Run<AsyncLockBenchmark>();
break;
}
Console.ReadLine();
@@ -5,7 +5,7 @@ using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark
@@ -17,7 +17,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark : BaseBenchmark
@@ -104,7 +104,7 @@ namespace MQTTnet.Benchmarks
return Task.FromResult(count);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
@@ -5,7 +5,7 @@ using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark
@@ -13,7 +13,7 @@ using MQTTnet.AspNetCore.Client.Tcp;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class TcpPipesBenchmark
{
@@ -8,7 +8,7 @@ using BenchmarkDotNet.Jobs;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark
@@ -15,13 +15,13 @@ namespace MQTTnet.Extensions.ManagedClient
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
event Func<EventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
event Func<EventArgs, Task> ConnectionStateChangedAsync;
event Func<EventArgs, Task> DisconnectedAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
@@ -18,7 +18,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -29,7 +29,7 @@
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -55,16 +55,9 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -51,9 +51,9 @@ namespace MQTTnet.Extensions.ManagedClient
/// cref="MaintainConnectionAsync" />
/// .
/// </summary>
readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
readonly Dictionary<string, MqttTopicFilter> _reconnectSubscriptions = new Dictionary<string, MqttTopicFilter>();
readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
@@ -93,7 +93,7 @@ namespace MQTTnet.Extensions.ManagedClient
remove => InternalClient.ApplicationMessageReceivedAsync -= value;
}
public event Func<EventArgs, Task> ConnectedAsync
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{
add => InternalClient.ConnectedAsync += value;
remove => InternalClient.ConnectedAsync -= value;
@@ -111,7 +111,7 @@ namespace MQTTnet.Extensions.ManagedClient
remove => _connectionStateChangedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> DisconnectedAsync
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
{
add => InternalClient.DisconnectedAsync += value;
remove => InternalClient.DisconnectedAsync -= value;
@@ -201,7 +201,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
finally
{
if (_applicationMessageSkippedEvent.HasHandlers)
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
{
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
}
@@ -289,7 +289,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
foreach (var topicFilter in topicFilters)
{
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
_subscriptions[topicFilter.Topic] = topicFilter;
_unsubscriptions.Remove(topicFilter.Topic);
}
}
@@ -455,18 +455,11 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (_reconnectSubscriptions.Any())
{
var subscriptions = _reconnectSubscriptions.Select(
i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
});
var topicFilters = new List<MqttTopicFilter>();
foreach (var sub in subscriptions)
foreach (var sub in _reconnectSubscriptions)
{
topicFilters.Add(sub);
topicFilters.Add(sub.Value);
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
{
@@ -495,14 +488,7 @@ namespace MQTTnet.Extensions.ManagedClient
lock (_subscriptions)
{
subscriptions = _subscriptions.Select(
i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
})
.ToList();
subscriptions = _subscriptions.Values.ToList();
_subscriptions.Clear();
unsubscriptions = new HashSet<string>(_unsubscriptions);
@@ -523,7 +509,7 @@ namespace MQTTnet.Extensions.ManagedClient
foreach (var subscription in subscriptions)
{
_reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
_reconnectSubscriptions[subscription.Topic] = subscription;
}
var addedTopicFilters = new List<MqttTopicFilter>();
@@ -18,7 +18,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -29,7 +29,7 @@
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -55,16 +55,9 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -18,7 +18,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -29,7 +29,7 @@
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -55,24 +55,17 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WebSocket4Net" Version="0.15.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
</Project>
@@ -98,7 +98,6 @@ namespace MQTTnet.Extensions.WebSocket4Net
#else
certificates.Add(certificate);
#endif
}
}
@@ -155,9 +154,9 @@ namespace MQTTnet.Extensions.WebSocket4Net
return Task.FromResult(readBytes);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
_webSocket.Send(buffer, offset, count);
_webSocket.Send(buffer.Array, buffer.Offset, buffer.Count);
return Task.FromResult(0);
}
+28
View File
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.TestApp
{
public sealed class AsyncLockTest
{
public async Task Run()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource())
{
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.WaitAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
}
}
}
}
@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
@@ -23,10 +23,4 @@
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Start.bat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+5
View File
@@ -28,6 +28,7 @@ namespace MQTTnet.TestApp
Console.WriteLine("c = Start QoS 0 benchmark");
Console.WriteLine("d = Start server with logging");
Console.WriteLine("e = Start Message Throughput Test");
Console.WriteLine("f = Start AsyncLock Test");
var pressedKey = Console.ReadKey(true);
if (pressedKey.KeyChar == '1')
@@ -88,6 +89,10 @@ namespace MQTTnet.TestApp
{
Task.Run(new MessageThroughputTest().Run);
}
else if (pressedKey.KeyChar == 'f')
{
Task.Run(new AsyncLockTest().Run);
}
Thread.Sleep(Timeout.Infinite);
}
+1 -1
View File
@@ -118,7 +118,7 @@ namespace MQTTnet.TestApp
{
if (e.ClientId == "SpecialClient")
{
if (e.Username != "USER" || e.Password != "PASS")
if (e.UserName != "USER" || e.Password != "PASS")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
-1
View File
@@ -1 +0,0 @@
dotnet MQTTnet.TestApp.NetCore.dll
+21 -16
View File
@@ -19,10 +19,12 @@ using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client
{
[TestClass]
public sealed class Client_Tests : BaseTestClass
public sealed class MqttClient_Tests : BaseTestClass
{
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
@@ -145,6 +147,7 @@ namespace MQTTnet.Tests.Client
}
catch
{
// Ignore errors.
}
await Task.Delay(500);
@@ -239,10 +242,10 @@ namespace MQTTnet.Tests.Client
for (var i = 0; i < 98; i++)
{
Assert.IsFalse(clients[i].IsConnected, $"clients[{i}] is not connected");
Assert.IsFalse(clients[i].TryPingAsync().GetAwaiter().GetResult(), $"clients[{i}] is not connected");
}
Assert.IsTrue(clients[99].IsConnected);
Assert.IsTrue(clients[99].TryPingAsync().GetAwaiter().GetResult());
Assert.AreEqual(1, clientStatus.Count);
Assert.AreEqual(1, sessionStatus.Count);
@@ -332,7 +335,7 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
}
}
@@ -343,11 +346,11 @@ namespace MQTTnet.Tests.Client
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
await client.DisconnectAsync();
Assert.IsFalse(client.IsConnected);
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult());
}
}
@@ -591,17 +594,17 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient();
await Task.Delay(500);
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(await client.TryPingAsync());
await server.StopAsync();
await Task.Delay(500);
Assert.IsFalse(client.IsConnected);
Assert.IsFalse(await client.TryPingAsync());
await server.StartAsync();
await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(await client.TryPingAsync());
}
}
@@ -637,6 +640,7 @@ namespace MQTTnet.Tests.Client
}
catch
{
// Ignore errors.
}
SpinWait.SpinUntil(() => tries >= maxTries, 10000);
@@ -656,11 +660,11 @@ namespace MQTTnet.Tests.Client
var client = await testEnvironment.ConnectClient();
await Task.Delay(500);
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
await server.StopAsync();
await Task.Delay(500);
Assert.IsFalse(client.IsConnected);
Assert.IsFalse(client.TryPingAsync().GetAwaiter().GetResult());
for (var i = 0; i < 5; i++)
{
@@ -671,6 +675,7 @@ namespace MQTTnet.Tests.Client
}
catch
{
// Ignore errors.
}
}
@@ -678,7 +683,7 @@ namespace MQTTnet.Tests.Client
await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(client.IsConnected);
await client.PingAsync();
}
}
@@ -828,10 +833,10 @@ namespace MQTTnet.Tests.Client
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
client.DisconnectedAsync += e =>
{
Assert.IsTrue(e.ClientWasConnected);
@@ -851,7 +856,7 @@ namespace MQTTnet.Tests.Client
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
Assert.IsTrue(client.TryPingAsync().GetAwaiter().GetResult());
client.DisconnectedAsync += e =>
{
Assert.IsTrue(e.ClientWasConnected);
@@ -941,7 +946,7 @@ namespace MQTTnet.Tests.Client
await Task.Delay(500);
Assert.IsTrue(messageReceived);
Assert.IsTrue(client1.IsConnected);
Assert.IsTrue(client1.TryPingAsync().GetAwaiter().GetResult());
Assert.IsFalse(disconnectedFired);
}
}
@@ -563,7 +563,7 @@ namespace MQTTnet.Tests
using (var headerStream = new MemoryStream(buffer1.Join().ToArray()))
{
using (var channel = new TestMqttChannel(headerStream))
using (var channel = new MemoryMqttChannel(headerStream))
{
using (var adapter = new MqttChannelAdapter(
channel,
@@ -583,7 +583,7 @@ namespace MQTTnet.Tests
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
{
var channel = new TestMqttChannel(buffer);
var channel = new MemoryMqttChannel(buffer);
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -603,7 +603,7 @@ namespace MQTTnet.Tests
var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer);
var buffer = serializer.Encode(packet);
using (var channel = new TestMqttChannel(buffer.Join().ToArray()))
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
{
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -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.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -13,6 +14,80 @@ namespace MQTTnet.Tests.Internal
[TestClass]
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]
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]
public void Lock_10_Parallel_Tasks()
public async Task Test_Cancellation_With_Later_Access()
{
const int ThreadsCount = 10;
var asyncLock = new AsyncLock();
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < ThreadsCount; i++)
var releaser = await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(async () =>
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
using (var releaser = await @lock.WaitAsync(CancellationToken.None))
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
await asyncLock.WaitAsync(timeout.Token).ConfigureAwait(false);
}
Assert.Fail("Exception should be thrown!");
}
catch (OperationCanceledException)
{
}
Task.WaitAll(threads);
Assert.AreEqual(ThreadsCount, globalI);
releaser.Dispose();
using (await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
// When the method finished, the thread got access.
}
}
}
}
}
+3 -3
View File
@@ -10,9 +10,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
+1 -1
View File
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.MQTTv5
var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1"));
var testMessageHandler = new TestApplicationMessageReceivedHandler(client1);
var testMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1);
await client1.SubscribeAsync("a");
@@ -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)
{
return testEnvironment.Factory.CreateClientOptionsBuilder()
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
@@ -10,16 +11,16 @@ using MQTTnet.Channel;
namespace MQTTnet.Tests.Mockups
{
public sealed class TestMqttChannel : IMqttChannel
public sealed class MemoryMqttChannel : IMqttChannel
{
readonly MemoryStream _stream;
public TestMqttChannel(MemoryStream stream)
public MemoryMqttChannel(MemoryStream stream)
{
_stream = stream;
}
public TestMqttChannel(byte[] buffer)
public MemoryMqttChannel(byte[] buffer)
{
_stream = new MemoryStream(buffer);
}
@@ -45,9 +46,9 @@ namespace MQTTnet.Tests.Mockups
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
return _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken);
}
public void Dispose()
@@ -1,22 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Packets;
namespace MQTTnet.Tests.Mockups
{
public sealed class MqttPacketAsserts
{
public void AssertIsConnectPacket(MqttPacket packet)
{
Assert.AreEqual(packet.GetType(), typeof(MqttConnectPacket));
}
public void AssertIsConnAckPacket(MqttPacket packet)
{
Assert.AreEqual(packet.GetType(), typeof(MqttConnAckPacket));
}
}
}
@@ -27,7 +27,7 @@ namespace MQTTnet.Tests
public MqttPacket Decode(MqttPacketBuffer buffer)
{
using (var channel = new TestMqttChannel(buffer.ToArray()))
using (var channel = new MemoryMqttChannel(buffer.ToArray()))
{
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
+29 -116
View File
@@ -798,38 +798,7 @@ namespace MQTTnet.Tests.Server
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]
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]
public async Task Use_Empty_Client_ID()
{
@@ -988,93 +937,57 @@ namespace MQTTnet.Tests.Server
}
[TestMethod]
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
public async Task Disconnect_Client_with_Reason()
{
using (var testEnvironment = CreateTestEnvironment())
{
var receivedMessagesCount = 0;
var disconnectPacketReceived = false;
string testClientId = null;
await testEnvironment.StartServer();
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
var c1 = await testEnvironment.ConnectClient();
c1.ApplicationMessageReceivedAsync += e =>
testEnvironment.Server.ClientConnectedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
testClientId = e.ClientId;
return PlatformAbstractionLayer.CompletedTask;
};
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
var client = testEnvironment.CreateClient();
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_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 =>
client.InspectPackage += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
if (e.Buffer.Length > 0)
{
if (e.Buffer[0] == (byte)Protocol.MqttControlPacketType.Disconnect << 4)
{
disconnectPacketReceived = true;
}
}
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.
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
await LongTestDelay();
var c2 = await testEnvironment.ConnectClient(clientOptions);
// Test client should be connected now
// C3 will do the connection takeover.
var c3 = await testEnvironment.ConnectClient(clientOptions);
Assert.IsTrue(testClientId != null);
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]
public async Task Will_Message_Send()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
await LongTestDelay();
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
// MQTT 3.1.1 client should not receive a disconnect packet
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);
Assert.IsFalse(disconnectPacketReceived, "Disconnect packet received when none is expected");
}
}
+124
View File
@@ -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);
}
}
}
}
@@ -16,67 +16,52 @@ namespace MQTTnet.Tests.Server
public sealed class Retained_Messages_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_Retained_Messages_Supported_V3()
public async Task Clear_Retained_Message_With_Empty_Payload()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.RetainAvailable);
}
}
[TestMethod]
public async Task Server_Reports_Retained_Messages_Supported_V5()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.RetainAvailable);
}
}
[TestMethod]
public async Task Retained_Messages_Flow()
{
using (var testEnvironment = CreateTestEnvironment())
{
var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build();
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
await c1.DisconnectAsync();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c1.PublishAsync(retainedMessage);
await Task.Delay(200);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(0);
}
}
[TestMethod]
public async Task Clear_Retained_Message_With_Null_Payload()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build());
await c1.DisconnectAsync();
await LongTestDelay();
for (var i = 0; i < 5; i++)
{
await c2.UnsubscribeAsync("r");
await Task.Delay(100);
messageHandler.AssertReceivedCountEquals(i);
await c2.SubscribeAsync("r");
await Task.Delay(100);
messageHandler.AssertReceivedCountEquals(i + 1);
}
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c2.DisconnectAsync();
await Task.Delay(200);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(0);
}
}
@@ -114,7 +99,7 @@ namespace MQTTnet.Tests.Server
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build());
await Task.Delay(500);
@@ -125,52 +110,102 @@ namespace MQTTnet.Tests.Server
}
[TestMethod]
public async Task Clear_Retained_Message_With_Empty_Payload()
public async Task Receive_Retained_Messages_From_Higher_Qos_Level()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
// Upload retained message.
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
await c1.PublishAsync(
new MqttApplicationMessageBuilder().WithTopic("retained")
.WithPayload(new byte[1])
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.WithRetainFlag()
.Build());
await c1.DisconnectAsync();
// Subscribe using a new client.
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await Task.Delay(200);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
// Using QoS 2 will lead to 1 instead because the publish was made with QoS level 1 (see 3.8.4 SUBSCRIBE Actions)!
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce });
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(0);
messageHandler.AssertReceivedCountEquals(1);
}
}
[TestMethod]
public async Task Clear_Retained_Message_With_Null_Payload()
public async Task Retained_Messages_Flow()
{
using (var testEnvironment = CreateTestEnvironment())
{
var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build();
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c1.PublishAsync(retainedMessage);
await c1.DisconnectAsync();
await LongTestDelay();
for (var i = 0; i < 5; i++)
{
await c2.UnsubscribeAsync("r");
await Task.Delay(100);
messageHandler.AssertReceivedCountEquals(i);
await c2.SubscribeAsync("r");
await Task.Delay(100);
messageHandler.AssertReceivedCountEquals(i + 1);
}
await c2.DisconnectAsync();
}
}
[TestMethod]
public async Task Server_Reports_Retained_Messages_Supported_V3()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
var client = testEnvironment.CreateClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build());
var connectResult = await client.ConnectAsync(
testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.Build());
await c1.DisconnectAsync();
Assert.IsTrue(connectResult.RetainAvailable);
}
}
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await Task.Delay(200);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
await Task.Delay(500);
[TestMethod]
public async Task Server_Reports_Retained_Messages_Supported_V5()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
messageHandler.AssertReceivedCountEquals(0);
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(
testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.Build());
Assert.IsTrue(connectResult.RetainAvailable);
}
}
}
+103 -28
View File
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
@@ -15,6 +16,104 @@ namespace MQTTnet.Tests.Server
[TestClass]
public sealed class Security_Tests : BaseTestClass
{
[TestMethod]
public async Task Do_Not_Affect_Authorized_Clients()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
await testEnvironment.StartServer();
var publishedApplicationMessages = new List<MqttApplicationMessage>();
testEnvironment.Server.InterceptingPublishAsync += eventArgs =>
{
lock (publishedApplicationMessages)
{
publishedApplicationMessages.Add(eventArgs.ApplicationMessage);
}
return PlatformAbstractionLayer.CompletedTask;
};
testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
{
if (eventArgs.UserName == "SECRET")
{
eventArgs.ReasonCode = MqttConnectReasonCode.Success;
}
else
{
eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized;
}
return PlatformAbstractionLayer.CompletedTask;
};
using (var validClient = testEnvironment.CreateClient())
{
await validClient.ConnectAsync(
testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort)
.WithCredentials("SECRET")
.WithClientId("CLIENT")
.Build());
await validClient.PublishStringAsync("HELLO 1");
// The following code tries to connect a new client with the same client ID but invalid
// credentials. This should block the second client but keep the first one up and running.
try
{
using (var invalidClient = testEnvironment.CreateClient())
{
await invalidClient.ConnectAsync(
testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort)
.WithCredentials("???")
.WithClientId("CLIENT")
.Build());
}
}
catch
{
// Ignore errors from the second client.
}
await LongTestDelay();
await validClient.PublishStringAsync("HELLO 2");
await LongTestDelay();
await validClient.PublishStringAsync("HELLO 3");
await LongTestDelay();
Assert.AreEqual(3, publishedApplicationMessages.Count);
Assert.AreEqual(1, testEnvironment.Server.GetClientsAsync().GetAwaiter().GetResult().Count);
}
}
}
[TestMethod]
public Task Handle_Wrong_Password()
{
return TestCredentials("UserName", "x");
}
[TestMethod]
public Task Handle_Wrong_UserName()
{
return TestCredentials("x", "Password1");
}
[TestMethod]
public Task Handle_Wrong_UserName_And_Password()
{
return TestCredentials("x", "x");
}
[TestMethod]
public async Task Use_Username_Null_Password_Empty()
{
@@ -29,10 +128,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithCredentials(username, password)
.Build();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(username, password).Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException));
@@ -41,25 +137,7 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
public Task Handle_Wrong_UserName()
{
return TestCredentials("x", "Password1");
}
[TestMethod]
public Task Handle_Wrong_Password()
{
return TestCredentials("UserName", "x");
}
[TestMethod]
public Task Handle_Wrong_UserName_And_Password()
{
return TestCredentials("x", "x");
}
private async Task TestCredentials(string userName, string password)
async Task TestCredentials(string userName, string password)
{
using (var testEnvironment = CreateTestEnvironment())
{
@@ -69,7 +147,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e =>
{
if (e.Username != "UserName1")
if (e.UserName != "UserName1")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
@@ -84,10 +162,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithCredentials(userName, password)
.Build();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
+96 -27
View File
@@ -14,7 +14,6 @@ using MQTTnet.Implementations;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Tests.Server
{
@@ -125,7 +124,9 @@ namespace MQTTnet.Tests.Server
}
[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())
{
@@ -133,7 +134,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion).WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
var hasReceive = false;
@@ -147,7 +148,10 @@ namespace MQTTnet.Tests.Server
// Try to connect 50 clients at the same time.
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
await LongTestDelay();
var connectedClients = clients.Where(c => c != null).Where(c => c.TryPingAsync().GetAwaiter().GetResult()).ToList();
Assert.AreEqual(1, connectedClients.Count);
@@ -162,26 +166,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive);
}
}
[TestMethod]
public async Task Manage_Session_MaxParallel()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var serverOptions = new MqttServerOptionsBuilder();
await testEnvironment.StartServer(serverOptions);
var options = new MqttClientOptionsBuilder().WithClientId("1");
var clients = await Task.WhenAll(Enumerable.Range(0, 10).Select(i => TryConnect(testEnvironment, options)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
}
}
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[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]
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
{
@@ -296,13 +365,13 @@ namespace MQTTnet.Tests.Server
return sendClient;
}
catch (Exception)
catch
{
return null;
}
}
async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
static async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
{
try
{
+26 -1
View File
@@ -12,6 +12,7 @@ using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
@@ -45,6 +46,30 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
public async Task Enqueue_Message_After_Subscription()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ClientSubscribedTopicAsync += e =>
{
server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build()));
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient();
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(client);
await client.SubscribeAsync("test_topic");
await LongTestDelay();
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
}
}
[TestMethod]
public async Task Intercept_Subscription()
{
@@ -101,7 +126,7 @@ namespace MQTTnet.Tests.Server
.WithTopicFilter("c", MqttQualityOfServiceLevel.ExactlyOnce)
.WithTopicFilter("d")
.Build();
var response = await client.SubscribeAsync(subscribeOptions);
Assert.AreEqual(subscribeOptions.TopicFilters.Count, response.Items.Count);
+71
View File
@@ -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);
}
}
}
}
+6 -3
View File
@@ -188,11 +188,14 @@ namespace MQTTnet.Adapter
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
await _channel.WriteAsync(packetBuffer.Packet.Array, packetBuffer.Packet.Offset, packetBuffer.Packet.Count, cancellationToken).ConfigureAwait(false);
if (packetBuffer.Payload.Count > 0)
{
await _channel.WriteAsync(packetBuffer.Payload.Array, packetBuffer.Payload.Offset, packetBuffer.Payload.Count, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
}
else
{
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
}
Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
+5 -1
View File
@@ -12,13 +12,17 @@ namespace MQTTnet.Channel
public interface IMqttChannel : IDisposable
{
string Endpoint { get; }
bool IsSecureConnection { get; }
X509Certificate2 ClientCertificate { get; }
Task ConnectAsync(CancellationToken cancellationToken);
Task DisconnectAsync(CancellationToken cancellationToken);
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken);
}
}
+18 -19
View File
@@ -7,32 +7,31 @@ namespace MQTTnet.Client
{
public interface IMqttClient : IDisposable
{
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
bool IsConnected { get; }
MqttClientOptions Options { get; }
Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default);
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken = default);
Task PingAsync(CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default);
}
}
File diff suppressed because it is too large Load Diff
@@ -14,6 +14,26 @@ namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
try
{
await client.PingAsync(cancellationToken).ConfigureAwait(false);
return true;
}
catch
{
// Ignore errors.
}
return false;
}
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
{
if (client == null)
@@ -98,6 +98,16 @@ namespace MQTTnet.Client
/// </summary>
public ushort TopicAliasMaximum { get; set; }
/// <summary>
/// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary
/// client.
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
/// correctly.
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
/// properly.
/// </summary>
public bool TryPrivate { get; set; } = true;
/// <summary>
/// Gets or sets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
@@ -96,16 +96,6 @@ namespace MQTTnet.Client
return this;
}
/// <summary>
/// Sets the timeout which will be applied at socket level and internal operations.
/// The default value is the same as for sockets in .NET in general.
/// </summary>
public MqttClientOptionsBuilder WithTimeout(TimeSpan value)
{
_options.Timeout = value;
return this;
}
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
{
if (uri == null)
@@ -274,8 +264,7 @@ namespace MQTTnet.Client
return this;
}
// TODO: Consider creating _MqttClientTcpOptionsBuilder_ as overload.
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
{
if (optionsBuilder == null)
@@ -289,6 +278,16 @@ namespace MQTTnet.Client
return this;
}
/// <summary>
/// Sets the timeout which will be applied at socket level and internal operations.
/// The default value is the same as for sockets in .NET in general.
/// </summary>
public MqttClientOptionsBuilder WithTimeout(TimeSpan value)
{
_options.Timeout = value;
return this;
}
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
{
_tlsParameters = parameters;
@@ -322,6 +321,20 @@ namespace MQTTnet.Client
return this;
}
/// <summary>
/// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary
/// client.
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
/// correctly.
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
/// properly.
/// </summary>
public MqttClientOptionsBuilder WithTryPrivate(bool tryPrivate = true)
{
_options.TryPrivate = true;
return this;
}
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
{
if (name is null)
@@ -379,6 +392,17 @@ namespace MQTTnet.Client
_options.WillPayload = willPayload;
return this;
}
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
{
if (string.IsNullOrEmpty(willPayload))
{
return WithWillPayload((byte[])null);
}
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
return this;
}
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
{
@@ -391,5 +415,46 @@ namespace MQTTnet.Client
_options.WillTopic = willTopic;
return this;
}
public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
{
_options.WillRetain = willRetain;
return this;
}
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
{
_options.WillContentType = willContentType;
return this;
}
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
{
_options.WillCorrelationData = willCorrelationData;
return this;
}
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
{
_options.WillResponseTopic = willResponseTopic;
return this;
}
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
{
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
return this;
}
public MqttClientOptionsBuilder WithWillUserProperty(string name, string value)
{
if (_options.WillUserProperties == null)
{
_options.WillUserProperties = new List<MqttUserProperty>();
}
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
return this;
}
}
}
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Net;
using System.Net.Sockets;
namespace MQTTnet.Client
@@ -10,8 +11,19 @@ namespace MQTTnet.Client
{
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;
/// <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 LingerOption LingerState { get; set; } = new LingerOption(true, 0);
@@ -7,23 +7,23 @@ using System.Net;
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 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 string Uri { get; set; }
public override string ToString()
{
return Uri;
}
}
}
}
@@ -4,7 +4,7 @@
namespace MQTTnet.Client
{
public class MqttClientWebSocketProxyOptions
public sealed class MqttClientWebSocketProxyOptions
{
public string Address { get; set; }
@@ -13,74 +13,83 @@ namespace MQTTnet.Client
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
{
readonly Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> _acknowledgeHandler;
int _isAcknowledged;
public MqttApplicationMessageReceivedEventArgs(
string clientId,
string clientId,
MqttApplicationMessage applicationMessage,
MqttPublishPacket publishPacket,
Func<MqttApplicationMessageReceivedEventArgs, CancellationToken, Task> acknowledgeHandler)
{
ClientId = clientId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PublishPacket = publishPacket;
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
_acknowledgeHandler = acknowledgeHandler;
}
internal MqttPublishPacket PublishPacket { get; set; }
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// Gets or sets whether the library should send MQTT ACK packets automatically if required.
/// </summary>
public bool AutoAcknowledge { get; set; } = true;
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public MqttApplicationMessage ApplicationMessage { get; }
public bool ProcessingFailed { get; set; }
/// <summary>
/// Gets or sets the reason code which will be sent to the server.
/// </summary>
public MqttApplicationMessageReceivedReasonCode ReasonCode { get; set; } = MqttApplicationMessageReceivedReasonCode.Success;
/// <summary>
/// Gets or sets the user properties which will be sent to the server in the ACK packet etc.
/// </summary>
public List<MqttUserProperty> ResponseUserProperties { get; } = new List<MqttUserProperty>();
/// <summary>
/// Gets or sets the reason string which will be sent to the server in the ACK packet.
/// </summary>
public string ResponseReasonString { get; set; }
/// <summary>
/// Gets or sets whether this message was handled.
/// This value can be used in user code for custom control flow.
/// Gets or sets whether this message was handled.
/// This value can be used in user code for custom control flow.
/// </summary>
public bool IsHandled { get; set; }
/// <summary>
/// Gets ir sets whether the library should send MQTT ACK packets automatically if required.
/// Gets the identifier of the MQTT packet
/// </summary>
public bool AutoAcknowledge { get; set; } = true;
public 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 Task AcknowledgeAsync(CancellationToken cancellationToken)
internal MqttPublishPacket PublishPacket { get; set; }
public Task AcknowledgeAsync(CancellationToken cancellationToken)
{
if (_acknowledgeHandler == null)
{
throw new NotSupportedException("Deferred acknowledgement of application message is not yet supported in MQTTnet server.");
}
if (Interlocked.CompareExchange(ref _isAcknowledged, 1, 0) == 0)
{
return _acknowledgeHandler(this, cancellationToken);
}
throw new InvalidOperationException("The application message is already acknowledged.");
}
}
}
}
@@ -4,6 +4,7 @@
using System;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Formatter
@@ -25,6 +26,7 @@ namespace MQTTnet.Formatter
SubscriptionIdentifiersAvailable = true,
SharedSubscriptionAvailable = false,
TopicAliasMaximum = ushort.MaxValue,
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce,
WildcardSubscriptionAvailable = true,
AuthenticationMethod = validatingConnectionEventArgs.AuthenticationMethod,
@@ -32,7 +34,11 @@ namespace MQTTnet.Formatter
AssignedClientIdentifier = validatingConnectionEventArgs.AssignedClientIdentifier,
ReasonString = validatingConnectionEventArgs.ReasonString,
ServerReference = validatingConnectionEventArgs.ServerReference,
UserProperties = validatingConnectionEventArgs.ResponseUserProperties
UserProperties = validatingConnectionEventArgs.ResponseUserProperties,
ResponseInformation = null,
MaximumPacketSize = 0, // Unlimited,
ReceiveMaximum = 0 // Unlimited
};
return connAckPacket;
@@ -33,7 +33,8 @@ namespace MQTTnet.Formatter
RequestResponseInformation = clientOptions.RequestResponseInformation,
SessionExpiryInterval = clientOptions.SessionExpiryInterval,
TopicAliasMaximum = clientOptions.TopicAliasMaximum,
UserProperties = clientOptions.UserProperties
UserProperties = clientOptions.UserProperties,
TryPrivate = clientOptions.TryPrivate
};
if (!string.IsNullOrEmpty(clientOptions.WillTopic))
@@ -95,6 +95,10 @@ namespace MQTTnet.Formatter
var protocolName = _bufferReader.ReadString();
var protocolLevel = _bufferReader.ReadByte();
// Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge).
// This flag is accepted but not yet used.
protocolLevel &= 0x7F;
if (protocolName == "MQTT")
{
if (protocolLevel == 5)
@@ -175,12 +175,18 @@ namespace MQTTnet.Formatter.V3
throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3.");
}
var tryPrivate = (protocolVersion & 0x80) > 0;
protocolVersion &= 0x7F;
if (protocolVersion != 3 && protocolVersion != 4)
{
throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3.");
}
var packet = new MqttConnectPacket();
var packet = new MqttConnectPacket
{
TryPrivate = tryPrivate
};
var connectFlags = _bufferReader.ReadByte();
if ((connectFlags & 0x1) > 0)
@@ -409,7 +415,14 @@ namespace MQTTnet.Formatter.V3
ValidateConnectPacket(packet);
bufferWriter.WriteString("MQIsdp");
bufferWriter.WriteByte(3); // Protocol Level 3
var protocolVersion = 3;
if (packet.TryPrivate)
{
protocolVersion |= 0x80;
}
bufferWriter.WriteByte((byte)protocolVersion);
byte connectFlags = 0x0;
if (packet.CleanSession)
@@ -72,11 +72,11 @@ namespace MQTTnet.Formatter.V5
if (value == MqttQualityOfServiceLevel.AtLeastOnce)
{
Write(MqttPropertyId.MaximumQoS, true);
Write(MqttPropertyId.MaximumQoS, 0x1);
}
else
{
Write(MqttPropertyId.MaximumQoS, false);
Write(MqttPropertyId.MaximumQoS, 0x0);
}
}
@@ -84,6 +84,11 @@ namespace MQTTnet.Formatter.V5
{
// If absent, the Application Message does not expire.
// This library uses 0 to indicate no expiration.
if (value == 0)
{
return;
}
WriteAsFourByteInteger(MqttPropertyId.MessageExpiryInterval, value);
}
@@ -108,12 +108,12 @@ namespace MQTTnet.Implementations
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
// In the write method only the internal buffer will be filled. So here is no
// async/await required. The real network transmit is done when calling the
// Flush method.
_writeStream.Write(buffer, offset, count);
_writeStream.Write(buffer.Array, buffer.Offset, buffer.Count);
return _writeStream.FlushAsync(cancellationToken);
}
@@ -3,6 +3,9 @@
// See the LICENSE file in the project root for more information.
#if !WINDOWS_UWP
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using System;
using System.IO;
using System.Net.Security;
@@ -11,9 +14,6 @@ using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Exceptions;
namespace MQTTnet.Implementations
{
@@ -68,6 +68,11 @@ namespace MQTTnet.Implementations
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily);
}
if (_tcpOptions.LocalEndpoint != null)
{
socket.Bind(_tcpOptions.LocalEndpoint);
}
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
socket.SendBufferSize = _tcpOptions.BufferSize;
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
@@ -77,7 +82,7 @@ namespace MQTTnet.Implementations
{
socket.LingerState = _tcpOptions.LingerState;
}
if (_tcpOptions.DualMode.HasValue)
{
// It is important to avoid setting the flag if no specific value is set by the user
@@ -103,15 +108,20 @@ namespace MQTTnet.Implementations
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
ClientCertificates = LoadCertificates(),
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
CertificateRevocationCheckMode =
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
TargetHost = _tcpOptions.Server,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
};
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
#else
await sslStream.AuthenticateAsClientAsync(_tcpOptions.Server, LoadCertificates(), _tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
await sslStream.AuthenticateAsClientAsync(
_tcpOptions.Server,
LoadCertificates(),
_tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
.ConfigureAwait(false);
#endif
}
catch
@@ -215,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();
@@ -229,12 +239,12 @@ namespace MQTTnet.Implementations
}
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
await stream.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false);
#else
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_disposeAction))
{
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken).ConfigureAwait(false);
}
#endif
}
@@ -269,6 +279,11 @@ namespace MQTTnet.Implementations
return certificateValidationHandler(eventArgs);
}
if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
{
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
}
return sslPolicyErrors == SslPolicyErrors.None;
}
@@ -134,7 +134,7 @@ namespace MQTTnet.Implementations
continue;
}
Task.Run(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken).RunInBackground(_logger);
_ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -2,15 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Channel;
using MQTTnet.Internal;
using System;
using System.Net;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
{
@@ -35,12 +35,12 @@ namespace MQTTnet.Implementations
ClientCertificate = clientCertificate;
}
public X509Certificate2 ClientCertificate { get; }
public string Endpoint { get; }
public bool IsSecureConnection { get; private set; }
public X509Certificate2 ClientCertificate { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{
var uri = _options.Uri;
@@ -89,14 +89,26 @@ namespace MQTTnet.Implementations
Cleanup();
}
public void Dispose()
{
Cleanup();
}
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
return response.Count;
}
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET5_0_OR_GREATER
// MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1].
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
#else
// The lock is required because the client will throw an exception if _SendAsync_ is
// called from multiple threads at the same time. But this issue only happens with several
// framework versions.
@@ -107,87 +119,9 @@ namespace MQTTnet.Implementations
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
}
}
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()
@@ -231,5 +165,77 @@ namespace MQTTnet.Implementations
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
#endif
}
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
{
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
{
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
}
if (_options.SubProtocols != null)
{
foreach (var subProtocol in _options.SubProtocols)
{
clientWebSocket.Options.AddSubProtocol(subProtocol);
}
}
if (_options.CookieContainer != null)
{
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
#if WINDOWS_UWP
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
}
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
#if NETSTANDARD1_3
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard1.3'.");
#elif NETSTANDARD2_0
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
var context = new MqttClientCertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context);
};
#endif
}
}
}
}
@@ -11,11 +11,11 @@ namespace MQTTnet.Implementations
public static class PlatformAbstractionLayer
{
#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];
#else
public static Task CompletedTask => Task.CompletedTask;
public static Task CompletedTask { get; } = Task.CompletedTask;
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
#endif
+50 -7
View File
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
@@ -13,7 +14,16 @@ namespace MQTTnet.Internal
{
readonly List<AsyncEventInvocator<TEventArgs>> _handlers = new List<AsyncEventInvocator<TEventArgs>>();
public bool HasHandlers => _handlers.Count > 0;
ICollection<AsyncEventInvocator<TEventArgs>> _handlersForInvoke;
public AsyncEvent()
{
_handlersForInvoke = _handlers;
}
// Track the existence of handlers in a separate field so that checking it all the time will not
// require locking the actual list (_handlers).
public bool HasHandlers { get; private set; }
public void AddHandler(Func<TEventArgs, Task> handler)
{
@@ -22,7 +32,13 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
lock (_handlers)
{
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
}
}
public void AddHandler(Action<TEventArgs> handler)
@@ -32,17 +48,32 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
lock (_handlers)
{
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
}
}
public async Task InvokeAsync(TEventArgs eventArgs)
{
foreach (var handler in _handlers)
if (!HasHandlers)
{
return;
}
// Adding or removing handlers will produce a new list instance all the time.
// So locking here is not required since only the reference to an immutable list
// of handlers is used.
var handlers = _handlersForInvoke;
foreach (var handler in handlers)
{
await handler.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
public void RemoveHandler(Func<TEventArgs, Task> handler)
{
if (handler == null)
@@ -50,7 +81,13 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.RemoveAll(h => h.WrapsHandler(handler));
lock (_handlers)
{
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
}
}
public void RemoveHandler(Action<TEventArgs> handler)
@@ -60,7 +97,13 @@ namespace MQTTnet.Internal
throw new ArgumentNullException(nameof(handler));
}
_handlers.RemoveAll(h => h.WrapsHandler(handler));
lock (_handlers)
{
_handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList());
}
}
public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger)
@@ -21,12 +21,14 @@ namespace MQTTnet.Internal
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)
{
return ReferenceEquals(_asyncHandler, handler);
// Do not use ReferenceEquals! It will not work with delegates.
return handler == _asyncHandler;
}
public Task InvokeAsync(TEventArgs eventArgs)
+143 -44
View File
@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
@@ -10,77 +12,174 @@ namespace MQTTnet.Internal
{
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 Task<IDisposable> _releaser;
SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
bool _isDisposed;
public AsyncLock()
{
_releaser = Task.FromResult((IDisposable)new Releaser(this));
}
public Task<IDisposable> WaitAsync(CancellationToken cancellationToken)
{
Task task;
// This lock is required to avoid ObjectDisposedExceptions.
// These are fired when this lock gets disposed (and thus the semaphore)
// and a worker thread tries to call this method at the same time.
// Another way would be catching all ObjectDisposedExceptions but this situation happens
// quite often when clients are disconnecting.
lock (_syncRoot)
{
task = _semaphore?.WaitAsync(cancellationToken);
}
if (task == null)
{
throw new ObjectDisposedException("The AsyncLock is disposed.");
}
if (task.Status == TaskStatus.RanToCompletion)
{
return _releaser;
}
// Wait for the _WaitAsync_ method and return the releaser afterwards.
return task.ContinueWith(
(_, state) => (IDisposable)state,
_releaser.Result,
cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
_releaserWithDirectApproval = new Releaser(this, null, CancellationToken.None);
_releaserTaskWithDirectApproval = Task.FromResult((IDisposable)_releaserWithDirectApproval);
}
public void Dispose()
{
lock (_syncRoot)
{
_semaphore?.Dispose();
_semaphore = null;
foreach (var waitingTask in _queuedTasks)
{
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)
{
_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
{
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()
{
_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.");
}
}
}
}
}
+3 -10
View File
@@ -29,7 +29,7 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Christian Kratky 2016-2022</Copyright>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -40,7 +40,7 @@
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591</NoWarn>
</PropertyGroup>
@@ -76,14 +76,7 @@
</ItemGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
@@ -64,6 +64,8 @@ namespace MQTTnet.Packets
public List<MqttUserProperty> WillUserProperties { get; set; }
public bool TryPrivate { get; set; }
public override string ToString()
{
var passwordText = string.Empty;
@@ -8,14 +8,20 @@ namespace MQTTnet.Server
{
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
{
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
SenderId = senderId;
}
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </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.
using System;
using System.Collections;
using MQTTnet.Formatter;
namespace MQTTnet.Server
{
public sealed class ClientConnectedEventArgs : EventArgs
{
/// <summary>
/// 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>
public string ClientId { get; internal set; }
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
ProtocolVersion = protocolVersion;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <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>
public string UserName { get; internal set; }
public string ClientId { get; }
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// Gets the endpoint of the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; internal set; }
public string Endpoint { get; }
/// <summary>
/// Gets the endpoint of the connected client.
/// Gets the protocol version which is used by the connected client.
/// </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.
using System;
using System.Collections;
namespace MQTTnet.Server
{
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>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; 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.
using System;
using System.Collections;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class ClientSubscribedTopicEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public ClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public 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.
using System;
using System.Collections;
namespace MQTTnet.Server
{
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public ClientUnsubscribedTopicEventArgs(string clientId, string topicFilter, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string 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.
using System;
using System.Collections;
using System.Threading;
using MQTTnet.Packets;
@@ -10,30 +11,44 @@ namespace MQTTnet.Server
{
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>
/// 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>
public string ClientId { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <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>
public string Endpoint { get; internal set; }
public string ClientId { get; }
/// <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>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <summary>
/// Gets the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public IDictionary SessionItems { get; }
}
}
@@ -10,18 +10,26 @@ namespace MQTTnet.Server
{
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; }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public string ClientId { get; }
public bool CloseConnection { get; set; }
@@ -38,6 +46,6 @@ namespace MQTTnet.Server
/// <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; internal set; }
public IDictionary SessionItems { get; }
}
}
@@ -12,16 +12,28 @@ namespace MQTTnet.Server
{
public sealed class InterceptingSubscriptionEventArgs : EventArgs
{
public InterceptingSubscriptionEventArgs(
CancellationToken cancellationToken,
string clientId,
MqttSessionStatus session,
MqttTopicFilter topicFilter)
{
CancellationToken = cancellationToken;
ClientId = clientId;
Session = session;
TopicFilter = topicFilter;
}
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public string ClientId { get; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
@@ -48,12 +60,12 @@ namespace MQTTnet.Server
/// <summary>
/// Gets the current client session.
/// </summary>
public MqttSessionStatus Session { get; internal set; }
public MqttSessionStatus Session { 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; internal set; }
public IDictionary SessionItems => Session.Items;
/// <summary>
/// Gets or sets the topic filter.
@@ -10,16 +10,24 @@ namespace MQTTnet.Server
{
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
{
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic)
{
CancellationToken = cancellationToken;
ClientId = clientId;
SessionItems = sessionItems;
Topic = topic;
}
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public string ClientId { get; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
@@ -41,7 +49,7 @@ namespace MQTTnet.Server
/// <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; internal set; }
public IDictionary SessionItems { get; }
/// <summary>
/// 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
/// separator).
/// </summary>
public string Topic { get; internal set; }
public string Topic { get; }
}
}
@@ -9,10 +9,17 @@ namespace MQTTnet.Server
{
public sealed class RetainedMessageChangedEventArgs : EventArgs
{
public string ClientId { get; internal set; }
public MqttApplicationMessage ChangedRetainedMessage { get; internal set; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; internal set; }
public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List<MqttApplicationMessage> storedRetainedMessages)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage));
StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages));
}
public MqttApplicationMessage ChangedRetainedMessage { get; }
public string ClientId { get; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; }
}
}

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