Compare commits

...
82 Commits
Author SHA1 Message Date
Christian ae84aa43d2 1846 server mqttserverstopasync doesnt indicate correct reason (#1872) 2023-11-25 16:17:16 +01:00
RidoandChristian b431a5d3fe Add ReasonCode to SendExtendedAuthenticationExchangeDataAsync (#1882)
* add reason code to ext auth

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-11-25 15:45:10 +01:00
Christian b9683c2943 Improve client disconnect (#1873)
Imrpove client disconnect
2023-11-05 15:15:36 +01:00
Christian 1ba8f74555 Update version 2023-11-04 13:40:28 +01:00
Christian ec637f7946 Handle unobserved tasks exceptions (#1871)
* Access exception so that it will not treated as unhandled

* Refactor code

* Improve unit tests

* Fix null reference exception

* Update ReleaseNotes.md
2023-11-04 13:39:16 +01:00
Christian 2599b3dfdc Update settings for tests project so that it will build on linux (#1860)
* Update settings for tests project so that it will build on linux

* Improve unit tests and samples

* Remove no longer required target frameworks for unit tests

* Update ci.yml
2023-11-01 09:34:18 +01:00
Christian d22caa7b70 Update readme (#1870)
* Update ReleaseNotes.md

* Apply project code style
2023-11-01 09:26:18 +01:00
Rido 01c90badf9 Support custom CA chain validation (#1851)
* Add CaFile based on SslStream CustomTrust

* change to cert collection

* do not require to disable revocation checks
2023-11-01 09:22:13 +01:00
Diego2098andChristian e4f605fbd5 Update MqttServer.cs (#1858)
* Update MqttServer.cs

clientID is null => RetainedMessageChangedEventArgs ctor throw new ArgumentNullException(nameof(clientId)

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-11-01 08:29:19 +01:00
Christian 6fdee9e0db Fix wrong TLS usage (#1833)
* Fix wrong usage of TLS options

* Fix public broker tests

* Update ReleaseNotes.md
2023-09-06 20:08:35 +02:00
Christian cae7206733 Fix NullReferenceException when calling Ping (#1834)
* Check connection status before doing ping

* Fix broken reconnect sample

* Update ReleaseNotes.md
2023-09-06 20:08:13 +02:00
Christian e2e7aacc09 Update version and release notes 2023-09-06 20:07:45 +02:00
Ramon Smits 0d3dd96f2a Samples that show concurrent processing (#1803)
Samples that show concurrent processing with a limit and concurrent processing without auto ACK
2023-09-01 19:01:49 +02:00
Tomáš Jecha e0660ba887 #1810 Close connection after failed connect. (#1811) 2023-09-01 18:58:36 +02:00
Ivan BeňovicandChristian 5965ded488 Add possibility to specify parameter to RPC invocation (#1798)
* Add possibility to specify parameter to RPC invocation which will be accessible in TopicGenerationContext.

* Make parameters optional

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-09-01 18:57:22 +02:00
Christian fc31c3077c Fix Unit Tests 2023-08-22 20:02:47 +02:00
Christian e604345e65 Update version 2023-08-19 15:23:10 +02:00
ChristianandSean Hanna 111a0d69cf Add support for hot reloading of client certificates (#1783)
* Refactor Unit Tests

* Add new certificate provider interface instead of read only certificates list.

* Update ReleaseNotes.md

* Fix Unit Tests

* Fix build

* unit testing hot swappable certificates (#1787)

* Move test classes to correct namespace

* Apply code style

---------

Co-authored-by: Sean Hanna <hannasm@users.noreply.github.com>
2023-08-19 15:14:05 +02:00
trokyandChristian 1a08ec17ba Added certificate validation callback for .NET framework 4.x (#1806)
* Added certificate validation callback for .NET framework 4.x

* Update ReleaseNotes.md

* Also expose sender to event arguments

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-08-19 14:20:01 +02:00
Ramon SmitsandChristian c537d290c3 Only logging "Client will now throw an _MqttConnectingFailedException_." warning when actually throwing the exception (#1801)
* Warning was always logged

Warning was always logged while the text states it would throw a `MqttConnectingFailedException` exception

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-08-19 13:59:14 +02:00
Cynthia MacLeodandChristian 2180f59725 Address issue #1818 where MqttConnectionContext throws an error when passed a TcpConnection. (#1819)
* Address issue #1818 where MqttConnectionContext throws and error when passed a TcpConnection.

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-08-19 13:48:01 +02:00
Ramon SmitsandChristian b1dc8607af Moved check on Options.ValidateFeatures below ThrowIf methods to prevent NullReferenceException (#1800)
* Moved check on Options.ValidateFeatures below ThrowIf methods to prevent NullReferenceException

* Add Unit Test

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-08-13 18:38:37 +02:00
simonthum 101898695e Add will message expiry interval to builder (#1804)
This was missing from builder but not regular options.
2023-08-13 18:06:16 +02:00
Christian 9295626503 [Server] Add initial support for session expiration (#1776) 2023-06-27 19:32:08 +02:00
Christian 18614d4e81 Expose configuration of packet fragmentation in server options. (#1769) 2023-06-15 18:51:43 +02:00
Christian 560e80455f Update samples (#1768) 2023-06-15 18:32:28 +02:00
HansM a97e88be29 Update Client_Subscribe_Samples.cs (#1766)
Fixes https://github.com/dotnet/MQTTnet/issues/1765.
2023-06-15 17:31:01 +02:00
logicaloudandChristian 3b5667c882 Fix #1761: MQTTRetainedMessageManager, return task with NULL result instead of NULL (#1762)
* Return task with NULL result instead of NULL

* move Task.FromResult call out of lock

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-06-12 21:52:54 +02:00
Nicke BroströmandChristian 0790e54794 Websockets no longer working in Blazor WebAssembly (#1755)
* Fix for websockets not applying properties unless explicitly set

* Added comment indicating why properties can't be set

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-06-12 21:48:27 +02:00
Christian 94f9fac2bd Update ReleaseNotes.md 2023-05-29 16:48:50 +02:00
Christian 8ad86928f1 1681 misleading signature of mqttclientconnectasync mqttclientconnectresultcode expected but throws exception (#1749) 2023-05-29 16:29:16 +02:00
Christian d8e5936d45 1724 nullreferenceexception on mqttclientconnectasync (#1748)
Update MqttClient.cs
2023-05-29 13:55:36 +02:00
Christian caa717544b 1742 some bug in mqttclientconnectionvalidatorhandle (#1746) 2023-05-29 13:53:42 +02:00
Christian 66b77f8d6d Merge branch 'master' of https://github.com/dotnet/MQTTnet 2023-05-29 13:47:09 +02:00
Christian ab5264c8ca 1738 userproperties in clientconnectedeventargs (#1747) 2023-05-29 13:45:44 +02:00
Christian db12309bae Update ReleaseNotes.md 2023-05-29 12:41:28 +02:00
Christian 2953e0dcc5 Improve payload to string conversion 2023-05-29 12:41:24 +02:00
Ramon Smits 1b18ac9fe3 Resolve ConvertPayloadToString resulting in ArgumentNullException (#1745)
Resolve breaking change where previously ConvertPayloadToString returned null if the payload was null
2023-05-29 12:38:33 +02:00
Christian 2cdad6622a Fix build 2023-05-14 09:14:37 +02:00
Andrei Kurosh 7601d11a7a UseDefaultCredentials options for WebSockets proxy configuration. (#1734) 2023-05-14 08:58:57 +02:00
Christian 0bb4c6ef09 Expose more TLS options (#1737) 2023-05-13 12:08:52 +02:00
Christian 449624fd72 Expose proper connect result in clients Disconnected event (#1728) 2023-05-06 12:49:41 +02:00
Christian 99f4f46062 Add new overloads for packet payload and will payload (#1721) 2023-05-04 22:19:06 +02:00
b08d11c069 replace lock/semaphore by readerwriterlockslim to avoid lock contenti… (#1716)
* replace lock/semaphore by readerwriterlockslim to avoid lock contention while dispatch messages

* Update MqttClientSubscriptionsManager.cs

removed comment out codes

* Update ReleaseNotes.md

---------

Co-authored-by: Zeheng Feng <hf944813@broadcom.com>
Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-05-01 13:53:15 +02:00
Christian ac41e838ec Merge branch 'master' of https://github.com/dotnet/MQTTnet 2023-05-01 12:20:26 +02:00
Christian 836ddf3bbc Update version 2023-05-01 12:20:20 +02:00
Christian 0e815c626c Add validation of string lengths (#1718) 2023-05-01 12:03:23 +02:00
Christian 4b7d035e9e Reset release notes 2023-04-29 12:37:36 +02:00
Christian cb3c44f48d Rename InspectPackage method (#1713) 2023-04-23 13:57:59 +02:00
Christian 8b3b9d33de Add overloads for options builder (#1711)
* Add new overload for options builder

* Refactor code

* Update ReleaseNotes.md
2023-04-11 22:11:16 +02:00
scottbrogden-iheartmediaandChristian 59d99509c8 Notify what subscription topics failed as part of the SynchronizingSu… (#1678)
* Notify what subscription topics failed as part of the SynchronizingSubscriptionsFailedAsync callback

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-04-08 14:03:58 +02:00
RossHNPCandChristian 2933b76237 ClientMessageQueueInterceptor continues with subscription processing. (#1685)
* ClientMessageQueueInterceptor continues with subscription processing.

* Update readme

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-04-06 17:55:33 +02:00
ChristianandMichi 36c6d8974e Allow disabling of packet fragmentation (#1709)
* Add compatibility option for AWS

* Fix UWP build

* Fix comment in MqttClientOptions (#1706)

* Refactor naming

* Fix wrong option value

---------

Co-authored-by: Michi <michael@volz.dev>
2023-04-06 17:54:36 +02:00
Christian d6cab76728 Add user properties to event args to interceptors for subscribing and unsubscribing (#1707)
Add user properties to event args
2023-04-06 17:31:42 +02:00
Christian b4f740f338 Update readme 2023-04-05 22:12:58 +02:00
Christian a004de25ac Fix unit test 2023-02-22 21:46:33 +01:00
老九 d0584413ca Add PayloadSegmentable feature (#1585) 2023-02-22 20:53:55 +01:00
Christian ba57537108 Fix running state handling in server (#1680)
Fix running state handling
2023-02-17 21:17:31 +01:00
Christian 6dcb110737 Add InterceptingClientEnqueueAsync event (#1679) 2023-02-17 19:13:06 +01:00
Christian b599a832da Add new method for reading a single retained message (#1676) 2023-02-15 19:46:37 +01:00
Christian 9cbd5dd778 Update ReleaseNotes.md 2023-01-28 13:08:24 +01:00
Christian ed31c51237 Code cleanup 2023-01-28 13:08:21 +01:00
Christian e01707d25e Improve samples 2023-01-28 13:08:08 +01:00
Jeffery MichaelandJeff Scherrer 73f118fe1c Moved the usage of certificate provider into the server listener (#1652)
* Moved the usage of certificate provider into the server listener so that each connection has the posibility of receiving a new SSL cert.

* Removed warning from unused field.

* Reduced changeds on original codebase.

---------

Co-authored-by: Jeff Scherrer <Jeff.Scherrer@vw.com>
2023-01-28 12:18:31 +01:00
Int32Overflow 0234918f30 Performance impact fix (#1661) 2023-01-28 12:12:20 +01:00
Jeffery Michael c90ba7e061 Fixed broken UWP build (#1655)
Added compiler declaritive to check for WINDOWS_UWP on IsAssignableFrom call.
2023-01-28 12:02:22 +01:00
Christian 2aa4219838 Expose server session items (#1638)
* Expose server session items

* Add support for custom session items for injected application messages
2023-01-19 20:15:15 +01:00
Günther Foidl b98417ab39 Avoid some full memory barriers (#1590)
* Avoid some full memory barriers

* Reset in stats itself

* Updated MqttClientStatistics

* Fixed order of members as artifact from merge-conflict
2023-01-19 20:08:51 +01:00
Christian ead58dd606 Update ci.yml 2022-12-28 13:59:05 +01:00
Christian a93e935585 Update ci.yml 2022-12-28 09:37:01 +01:00
Christian aeb07e880e Update ReleaseNotes.md 2022-12-28 09:25:33 +01:00
Jeffery Michael e0ce1f309e dotnet 7 packages (#1636)
* Added dotnet 7 support/builds.

* Added NETCOREAPP_3_1_OR_GREATER where applicable.
2022-12-28 09:20:11 +01:00
Christian d74c81c3d1 Fix unit tests 2022-12-23 20:01:13 +01:00
Christian 2fa7200fb3 1544 add underlying socketoption of tcp keepalive into the server option (#1606)
* Expose TCP keep alive options to options builder

* Update ReleaseNotes.md
2022-12-23 17:15:49 +01:00
Christian 9f7d15e1b1 Update ReleaseNotes.md 2022-12-23 17:13:25 +01:00
Christian fdf00f1b18 More options for disconnect (#1557)
* Expose more options in disconnect options.

* Expose more options in disconnect API.

* Refactor code.

* Expose user properties in disconnected event.

* Expose more disconnect options in server.

* Fix build errors

* Add new samples.

* Fix client cleanup

* Fix merge conflicts
2022-12-23 17:09:02 +01:00
ghord abe7bbda15 Pass correct clientId to Unsubscribe interceptor (#1631) 2022-12-23 17:02:03 +01:00
ChristianandHansM 795139d78f Add feature validations (#1527)
* Add feature validation.

* Update ReleaseNotes.md

* Add validators or subscribe and unsubscribe.

* Add feature validation for disconnect.

* Fix build errors.

* Fix build errors

Co-authored-by: HansM <HansM2013@gmx.de>
2022-12-23 16:56:11 +01:00
Christian c7390ef0d7 Update verison 2022-12-23 16:51:13 +01:00
老九andGünther Foidl 8a926a3db3 Performance optimization of SendPacketAsync (#1624)
* performance optimization of SendPacketAsync

* Update Source/MQTTnet.AspnetCore/MqttConnectionContext.cs

Co-authored-by: Günther Foidl <gue@korporal.at>

Co-authored-by: Günther Foidl <gue@korporal.at>
2022-12-16 17:13:11 +01:00
soyoo 7d5e49b44c A typo in comment. (#1628) 2022-12-16 17:12:46 +01:00
Christian 555c3eac7a Reset release notes 2022-12-13 17:15:43 +01:00
193 changed files with 6205 additions and 2425 deletions
+7 -9
View File
@@ -1,9 +1,7 @@
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger). * [Client] Added support for custom CA chain validation (#1851, thanks to @rido-min).
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593). * [Client] Added support for custom CA chain validation (#1851, thanks to @rido-min).
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang). * [Client] Fixed handling of unobserved tasks exceptions (#1871).
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun). * [Client] Fixed not specified ReasonCode when using _SendExtendedAuthenticationExchangeDataAsync_ (#1882, thanks to @rido-min).
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang). * [Server] Fixed not working _UpdateRetainedMessageAsync_ public api (#1858, thanks to @kimdiego2098).
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang). * [Server] Added support for custom DISCONNECT packets when stopping the server or disconnect a client (BREAKING CHANGE!, #1846).
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang). * [Server] Added new property to stop the server from accepting new connections even if it is running (#1846).
* [Server] Add support for interception of will messages (#1613).
* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
+3 -2
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.1.4.${{github.run_number}}" VERSION: "4.3.2.${{github.run_number}}"
jobs: jobs:
build: build:
@@ -22,6 +22,7 @@ jobs:
dotnet-version: | dotnet-version: |
3.1.x 3.1.x
6.0.x 6.0.x
7.0.x
- name: Setup MSBuild - name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1 uses: microsoft/setup-msbuild@v1.1
@@ -53,7 +54,7 @@ jobs:
uses: darenm/Setup-VSTest@v1 uses: darenm/Setup-VSTest@v1
- name: Core Tests - name: Core Tests
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net7.0\MQTTnet.Tests.dll
- name: ASP.NET Tests - name: ASP.NET Tests
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
+1
View File
@@ -240,6 +240,7 @@ See the LICENSE file in the project root for more information.</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=catched/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
+16
View File
@@ -50,6 +50,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients * Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Getting Started
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
Use these command in the Package Manager console to install MQTTnet manually:
```
Install-Package MQTTnet
```
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+123 -23
View File
@@ -7,6 +7,7 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net; using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -16,6 +17,28 @@ namespace MQTTnet.Samples.Client;
public static class Client_Connection_Samples public static class Client_Connection_Samples
{ {
public static async Task Clean_Disconnect()
{
/*
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
* to the server and close the connection afterwards.
*
* See sample _Connect_Client_ for more details.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
// connection is closed in a "not clean" way. See MQTT specification for more details.
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
}
}
public static async Task Connect_Client() public static async Task Connect_Client()
{ {
/* /*
@@ -113,15 +136,15 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io") var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
.WithTls( .WithTlsOptions(
o => o =>
{ {
// The used public broker sometimes has invalid certificates. This sample accepts all // The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments. // certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true; o.WithCertificateValidationHandler(_ => true);
// The default value is determined by the OS. Set manually to force version. // The default value is determined by the OS. Set manually to force version.
o.SslProtocol = SslProtocols.Tls12; o.WithSslProtocols(SslProtocols.Tls12);
}) })
.Build(); .Build();
@@ -148,7 +171,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -170,7 +193,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -193,13 +216,11 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithTls( .WithTlsOptions(
o => o => o.WithCertificateValidationHandler(
{
// The used public broker sometimes has invalid certificates. This sample accepts all // The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments. // certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true; _ => true))
})
.Build(); .Build();
// In MQTTv5 the response contains much more information. // In MQTTv5 the response contains much more information.
@@ -214,6 +235,31 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_With_Amazon_AWS()
{
/*
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
*
* The broker requires special settings which are set here.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important!
.WithoutPacketFragmentation()
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("The MQTT client is connected.");
await mqttClient.DisconnectAsync();
}
}
public static async Task Disconnect_Clean() public static async Task Disconnect_Clean()
{ {
/* /*
@@ -232,7 +278,7 @@ public static class Client_Connection_Samples
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection. // Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0! // Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError); await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
} }
} }
@@ -269,18 +315,19 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883) var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTls( .WithTlsOptions(
o => o =>
{ {
o.CertificateValidationHandler = eventArgs => o.WithCertificateValidationHandler(
{ eventArgs =>
eventArgs.Certificate.Subject.DumpToConsole(); {
eventArgs.Certificate.GetExpirationDateString().DumpToConsole(); eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole(); eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole(); eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole(); eventArgs.Chain.ChainStatus.DumpToConsole();
return true; eventArgs.SslPolicyErrors.DumpToConsole();
}; return true;
});
}) })
.Build(); .Build();
@@ -297,7 +344,7 @@ public static class Client_Connection_Samples
/* /*
* This sample sends a PINGREQ packet to the server and waits for a reply. * This sample sends a PINGREQ packet to the server and waits for a reply.
* *
* This is only supported in METTv5.0.0+. * This is only supported in MQTTv5.0.0+.
*/ */
var mqttFactory = new MqttFactory(); var mqttFactory = new MqttFactory();
@@ -384,6 +431,59 @@ public static class Client_Connection_Samples
} }
} }
}); });
Console.WriteLine("Press <Enter> to exit");
Console.ReadLine();
} }
} }
}
public static async Task ConnectTls_WithCaFile()
{
var mqttFactory = new MqttFactory();
X509Certificate2Collection caChain = new X509Certificate2Collection();
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions(new MqttClientTlsOptionsBuilder()
.WithTrustChain(caChain)
.Build())
.Build();
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
}
}
const string mosquitto_org = @"
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE-----
";
}
@@ -5,6 +5,7 @@
// ReSharper disable UnusedType.Global // ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -176,4 +177,62 @@ public static class Client_Subscribe_Samples
response.DumpToConsole(); response.DumpToConsole();
} }
} }
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
*/
mqttClient.ApplicationMessageReceivedAsync += ea =>
{
ea.AutoAcknowledge = false;
async Task ProcessAsync()
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
await ea.AcknowledgeAsync(shutdownToken);
// WARNING: If process failures are not transient the message will be retried on every restart of the client
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
// the broker know processing failed
//
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
}
_ = Task.Run(ProcessAsync, shutdownToken);
return Task.CompletedTask;
};
}
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing, with:
* - a maximum concurrency limit based on Environment.ProcessorCount
*/
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
mqttClient.ApplicationMessageReceivedAsync += async ea =>
{
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
async Task ProcessAsync()
{
try
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
}
finally
{
concurrent.Release();
}
}
_ = Task.Run(ProcessAsync, shutdownToken);
};
}
} }
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
mqttClient.InspectPackage += OnInspectPackage; mqttClient.InspectPacketAsync += OnInspectPacket;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
} }
} }
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
{ {
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound) if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
{ {
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
+18 -1
View File
@@ -4,7 +4,13 @@
using System.Reflection; using System.Reflection;
Console.WriteLine("Welcome to MQTTnet samples!"); Console.BackgroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Red;
Console.Title = "Samples - MQTTnet";
Console.WriteLine("----------------------------------------------");
Console.WriteLine("- Welcome to MQTTnet samples! -");
Console.WriteLine("----------------------------------------------");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList(); var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample class (press Enter to continue): "); Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine(); var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0"); var selectedIndex = int.Parse(input ?? "0");
var selectedSampleClass = sampleClasses[selectedIndex]; var selectedSampleClass = sampleClasses[selectedIndex];
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample (press Enter to continue): "); Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor();
input = Console.ReadLine(); input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0"); selectedIndex = int.Parse(input ?? "0");
var selectedSampleMethod = sampleMethods[selectedIndex]; var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Executing sample..."); Console.WriteLine("Executing sample...");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
try try
@@ -17,7 +17,7 @@ using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
public static class Server_Asp_Net_Samples public static class Server_ASP_NET_Samples
{ {
public static Task Start_Server_With_WebSockets_Support() public static Task Start_Server_With_WebSockets_Support()
{ {
@@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
// ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local
public static class Server_Diagnostics_Samples
{
public static async Task Get_Notified_When_Client_Received_Message()
{
/*
* This sample starts a MQTT server and attaches an event which gets fired whenever
* a client received and acknowledged a PUBLISH packet.
*/
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
// Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
// It is also possible to read additional data from the client response. This requires casting the response packet.
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
}
}
@@ -7,6 +7,8 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Text.Json; using System.Text.Json;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
{ {
try try
{ {
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath)); var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded."); Console.WriteLine("Retained messages loaded.");
} }
catch (FileNotFoundException) catch (FileNotFoundException)
@@ -56,7 +61,9 @@ public static class Server_Retained_Messages_Samples
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list // used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event. // of retained messages must be provided in the loaded event.
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages); var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer); await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved."); Console.WriteLine("Retained messages saved.");
} }
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
Console.ReadLine(); Console.ReadLine();
} }
} }
sealed class MqttRetainedMessageModel
{
public string? ContentType { get; set; }
public byte[]? CorrelationData { get; set; }
public byte[]? Payload { get; set; }
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public string? ResponseTopic { get; set; }
public string? Topic { get; set; }
public List<MqttUserProperty>? UserProperties { get; set; }
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return new MqttRetainedMessageModel
{
Topic = message.Topic,
// Create a copy of the buffer from the payload segment because
// it cannot be serialized and deserialized with the JSON serializer.
Payload = message.PayloadSegment.ToArray(),
UserProperties = message.UserProperties,
ResponseTopic = message.ResponseTopic,
CorrelationData = message.CorrelationData,
ContentType = message.ContentType,
PayloadFormatIndicator = message.PayloadFormatIndicator,
QualityOfServiceLevel = message.QualityOfServiceLevel
// Other properties like "Retain" are not if interest in the storage.
// That's why a custom model makes sense.
};
}
public MqttApplicationMessage ToApplicationMessage()
{
return new MqttApplicationMessage
{
Topic = Topic,
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
PayloadFormatIndicator = PayloadFormatIndicator,
ResponseTopic = ResponseTopic,
CorrelationData = CorrelationData,
ContentType = ContentType,
UserProperties = UserProperties,
QualityOfServiceLevel = QualityOfServiceLevel,
Dup = false,
Retain = true
};
}
}
} }
+75
View File
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
public static class Server_TLS_Samples
{
public static async Task Run_Server_With_Self_Signed_Certificate()
{
/*
* This sample starts a simple MQTT server which will accept any TCP connection.
* It also has an encrypted connection using a self signed TLS certificate.
*
* See sample "Run_Minimal_Server" for more details.
*/
var mqttFactory = new MqttFactory();
// This certificate is self signed so that
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
}
static X509Certificate2 CreateSelfSignedCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null!,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
}
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe; connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection); var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false); await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync(); var readResult = await pipe.Send.Reader.ReadAsync();
Assert.IsTrue(readResult.Buffer.Length > 20000); Assert.IsTrue(readResult.Buffer.Length > 20000);
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
{ {
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join(); var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count); var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool); _sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool); _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
} }
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
public override ValueTask DisposeAsync() public override ValueTask DisposeAsync()
#else #else
public Task DisposeAsync() public Task DisposeAsync()
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose(); _socket?.Dispose();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
return base.DisposeAsync(); return base.DisposeAsync();
} }
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName> <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace> <RootNamespace>MQTTnet.AspNetCore</RootNamespace>
@@ -51,11 +51,11 @@
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants> <DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' "> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' "> <ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup> </ItemGroup>
@@ -33,8 +33,11 @@ namespace MQTTnet.AspNetCore
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
_connection = connection ?? throw new ArgumentNullException(nameof(connection)); _connection = connection ?? throw new ArgumentNullException(nameof(connection));
_input = connection.Transport.Input; if (!(_connection is TcpConnection tcp) || tcp.IsConnected)
_output = connection.Transport.Output; {
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
} }
public long BytesReceived { get; private set; } public long BytesReceived { get; private set; }
@@ -211,8 +214,17 @@ namespace MQTTnet.AspNetCore
{ {
var buffer = PacketFormatterAdapter.Encode(packet); var buffer = PacketFormatterAdapter.Encode(packet);
WritePacketBuffer(_output, buffer); if (buffer.Payload.Count == 0)
await _output.FlushAsync(cancellationToken).ConfigureAwait(false); {
// zero copy
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
}
else
{
WritePacketBuffer(_output, buffer);
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
}
BytesSent += buffer.Length; BytesSent += buffer.Length;
} }
+14 -6
View File
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -14,20 +15,27 @@ namespace MQTTnet.AspNetCore
{ {
public sealed class MqttHostedServer : MqttServer, IHostedService public sealed class MqttHostedServer : MqttServer, IHostedService
{ {
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) readonly MqttFactory _mqttFactory;
: base(options, adapters, logger)
public MqttHostedServer(MqttFactory mqttFactory, MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
options,
adapters,
logger)
{ {
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
} }
public Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
_ = StartAsync(); // The yield makes sure that the hosted service is considered up and running.
return Task.CompletedTask; await Task.Yield();
_ = StartAsync();
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
return StopAsync(); return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
} }
} }
} }
@@ -2,38 +2,34 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
using System; using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{ {
IMqttNetLogger _logger = new MqttNetNullLogger(); IMqttNetLogger _logger = MqttNetNullLogger.Instance;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; } public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public void Dispose()
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
{ {
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); if (webSocket == null)
{
throw new ArgumentNullException(nameof(webSocket));
}
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate); var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger)) using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
{ {
await clientHandler(channelAdapter).ConfigureAwait(false); await clientHandler(channelAdapter).ConfigureAwait(false);
} }
@@ -60,8 +56,15 @@ namespace MQTTnet.AspNetCore
} }
} }
public void Dispose() public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
} }
} }
@@ -4,7 +4,7 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection.Extensions;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations; using MQTTnet.Implementations;
@@ -14,86 +14,73 @@ namespace MQTTnet.AspNetCore
{ {
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
{
if (serviceCollection is null)
{
throw new ArgumentNullException(nameof(serviceCollection));
}
serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure);
return serviceCollection;
}
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options) public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
services.AddSingleton(options); services.AddSingleton(options);
services.AddHostedMqttServer(); services.AddHostedMqttServer();
return services; return services;
} }
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null) public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
{ {
services.AddSingleton(s => if (services == null)
{ {
var serverOptionsBuilder = new MqttServerOptionsBuilder(); throw new ArgumentNullException(nameof(services));
configure?.Invoke(serverOptionsBuilder); }
return serverOptionsBuilder.Build();
});
services.AddHostedMqttServer(); if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
return services; var serverOptionsBuilder = new MqttServerOptionsBuilder();
configure.Invoke(serverOptionsBuilder);
var options = serverOptionsBuilder.Build();
return AddHostedMqttServer(services, options);
}
public static void AddHostedMqttServer(this IServiceCollection services)
{
// The user may have these services already registered.
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
services.TryAddSingleton(new MqttFactory());
services.AddSingleton<MqttHostedServer>();
services.AddHostedService<MqttHostedServer>();
} }
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure) public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{ {
services.AddSingleton(s => if (services == null)
{ {
var builder = new AspNetMqttServerOptionsBuilder(s); throw new ArgumentNullException(nameof(services));
configure(builder); }
return builder.Build();
}); services.AddSingleton(
s =>
{
var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
return builder.Build();
});
services.AddHostedMqttServer(); services.AddHostedMqttServer();
return services; return services;
} }
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
{
var logger = new MqttNetEventLogger();
services.AddSingleton<IMqttNetLogger>(logger);
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
return services;
}
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttWebSocketServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
return services;
}
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
return services;
}
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services) public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
{ {
services.AddSingleton<MqttConnectionHandler>(); services.AddSingleton<MqttConnectionHandler>();
@@ -101,5 +88,44 @@ namespace MQTTnet.AspNetCore
return services; return services;
} }
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton(logger);
}
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
{
if (serviceCollection is null)
{
throw new ArgumentNullException(nameof(serviceCollection));
}
serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure);
return serviceCollection;
}
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
return services;
}
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttWebSocketServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
return services;
}
} }
} }
@@ -73,7 +73,7 @@ namespace MQTTnet.Benchmarks
var channel = new MemoryMqttChannel(_stream); var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger()); _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
} }
static byte[] Join(params ArraySegment<byte>[] chunks) static byte[] Join(params ArraySegment<byte>[] chunks)
@@ -4,7 +4,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<LangVersion>7.2</LangVersion> <LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
@@ -26,6 +26,10 @@
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" /> <PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" /> <ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" /> <ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
var writer = new MqttBufferWriter(4096, 65535); var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); _readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
} }
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589); writer.WriteVariableByteInteger(8574589);
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload); writer.WriteBinary(_demoPayload);
writer.Reset(0); writer.Reset(0);
} }
@@ -0,0 +1,68 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter;
using System;
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark
{
MemoryStream stream;
MqttPacketBuffer buffer;
[GlobalSetup]
public void GlobalSetup()
{
stream = new MemoryStream(1024);
var packet = new ArraySegment<byte>(new byte[10]);
buffer = new MqttPacketBuffer(packet);
}
[Benchmark(Baseline = true)]
public async ValueTask Before()
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
WritePacketBuffer(output, buffer);
await output.FlushAsync();
}
[Benchmark]
public async ValueTask After()
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
if (buffer.Payload.Count == 0)
{
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
}
else
{
WritePacketBuffer(output, buffer);
await output.FlushAsync().ConfigureAwait(false);
}
}
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
output.Advance(buffer.Length);
}
}
}
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
public void Deserialize_10000_Messages() public void Deserialize_10000_Messages()
{ {
var channel = new BenchmarkMqttChannel(_serializedPacket); var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger()); var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName> <AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime; return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
} }
async Task HandleSubscriptionExceptionAsync(Exception exception) async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{ {
_logger.Warning(exception, "Synchronizing subscriptions failed."); _logger.Warning(exception, "Synchronizing subscriptions failed.");
if (_synchronizingSubscriptionsFailedEvent.HasHandlers) if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
{ {
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
} }
} }
@@ -457,11 +457,13 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
_logger.Info("Publishing subscriptions at reconnect"); _logger.Info("Publishing subscriptions at reconnect");
List<MqttTopicFilter> topicFilters = null;
try try
{ {
if (_reconnectSubscriptions.Any()) if (_reconnectSubscriptions.Any())
{ {
var topicFilters = new List<MqttTopicFilter>(); topicFilters = new List<MqttTopicFilter>();
foreach (var sub in _reconnectSubscriptions) foreach (var sub in _reconnectSubscriptions)
{ {
@@ -479,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
} }
} }
@@ -591,6 +593,9 @@ namespace MQTTnet.Extensions.ManagedClient
} }
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false); await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
} }
if (addedSubscriptions != null && addedSubscriptions.Any()) if (addedSubscriptions != null && addedSubscriptions.Any())
@@ -607,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
} }
} }
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
/// <summary> /// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet. /// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon AWS limits this number to 8. The default is int.MaxValue. /// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
/// </summary> /// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue; public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
} }
@@ -2,17 +2,33 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using MQTTnet.Packets;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public class ManagedProcessFailedEventArgs : EventArgs public class ManagedProcessFailedEventArgs : EventArgs
{ {
public ManagedProcessFailedEventArgs(Exception exception) public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{ {
Exception = exception ?? throw new ArgumentNullException(nameof(exception)); Exception = exception ?? throw new ArgumentNullException(nameof(exception));
if (addedSubscriptions != null)
{
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
}
if (removedSubscriptions != null)
{
RemovedSubscriptions = new List<string>(removedSubscriptions);
}
} }
public Exception Exception { get; } public Exception Exception { get; }
public List<string> AddedSubscriptions { get; }
public List<string> RemovedSubscriptions { get; }
} }
} }
@@ -12,22 +12,37 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null) public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{ {
if (factory == null) throw new ArgumentNullException(nameof(factory)); if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null) if (mqttClient == null)
{ {
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger); return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
} }
return new ManagedMqttClient(mqttClient, factory.DefaultLogger); return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
} }
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger) public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{ {
if (factory == null) throw new ArgumentNullException(nameof(factory)); if (factory == null)
if (logger == null) throw new ArgumentNullException(nameof(logger)); {
throw new ArgumentNullException(nameof(factory));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger); return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
} }
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
{
return new ManagedMqttClientOptionsBuilder();
}
} }
} }
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -7,8 +8,8 @@ namespace MQTTnet.Extensions.Rpc
{ {
public interface IMqttRpcClient : IDisposable public interface IMqttRpcClient : IDisposable
{ {
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel); Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default); Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
} }
} }
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName> <AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
+19 -13
View File
@@ -4,10 +4,13 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -17,9 +20,9 @@ namespace MQTTnet.Extensions.Rpc
{ {
readonly IMqttClient _mqttClient; readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options; readonly MqttRpcClientOptions _options;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>(); readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options) public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{ {
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -40,13 +43,13 @@ namespace MQTTnet.Extensions.Rpc
_waitingCalls.Clear(); _waitingCalls.Clear();
} }
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
{ {
using (var timeoutToken = new CancellationTokenSource(timeout)) using (var timeoutToken = new CancellationTokenSource(timeout))
{ {
try try
{ {
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false); return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
} }
catch (OperationCanceledException exception) catch (OperationCanceledException exception)
{ {
@@ -60,14 +63,14 @@ namespace MQTTnet.Extensions.Rpc
} }
} }
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default) public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{ {
if (methodName == null) if (methodName == null)
{ {
throw new ArgumentNullException(nameof(methodName)); throw new ArgumentNullException(nameof(methodName));
} }
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel); var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic; var requestTopic = topicNames.RequestTopic;
@@ -83,11 +86,14 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty."); throw new MqttProtocolViolationException("RPC response topic is empty.");
} }
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic) var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel) if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
.WithResponseTopic(responseTopic) {
.Build(); requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try try
{ {
@@ -115,7 +121,6 @@ namespace MQTTnet.Extensions.Rpc
finally finally
{ {
_waitingCalls.TryRemove(responseTopic, out _); _waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false); await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
} }
} }
@@ -127,7 +132,8 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance; return CompletedTask.Instance;
} }
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload); var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc. // Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true; eventArgs.IsHandled = true;
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -11,13 +12,13 @@ namespace MQTTnet.Extensions.Rpc
{ {
public static class MqttRpcClientExtensions public static class MqttRpcClientExtensions
{ {
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client == null) throw new ArgumentNullException(nameof(client));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel); return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
} }
} }
} }
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -10,9 +11,11 @@ namespace MQTTnet.Extensions.Rpc
{ {
public sealed class TopicGenerationContext public sealed class TopicGenerationContext
{ {
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel) public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
{ {
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel; QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
@@ -20,6 +23,8 @@ namespace MQTTnet.Extensions.Rpc
public string MethodName { get; } public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; } public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; } public MqttRpcClientOptions Options { get; }
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName> <AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -47,13 +47,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
var uri = _webSocketOptions.Uri; var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{ {
if (_webSocketOptions.TlsOptions?.UseTls == false) if (_webSocketOptions.TlsOptions?.UseTls == true)
{ {
uri = "ws://" + uri; uri = "wss://" + uri;
} }
else else
{ {
uri = "wss://" + uri; uri = "ws://" + uri;
} }
} }
@@ -92,18 +92,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
var webSocketVersion = WebSocketVersion.None; var webSocketVersion = WebSocketVersion.None;
var receiveBufferSize = 0; var receiveBufferSize = 0;
var certificates = new X509CertificateCollection(); var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
if (_webSocketOptions.TlsOptions?.Certificates != null)
{
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
{
#if WINDOWS_UWP
certificates.Add(new X509Certificate(certificate));
#else
certificates.Add(certificate);
#endif
}
}
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize) _webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
{ {
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter( return new MqttChannelAdapter(
new MqttTcpChannel(options), new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector, logger)
logger); {
PacketInspector = packetInspector
};
} }
case MqttClientWebSocketOptions webSocketOptions: case MqttClientWebSocketOptions webSocketOptions:
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter( return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions), new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector, logger)
logger); {
PacketInspector = packetInspector
};
} }
default: default:
+10 -1
View File
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
var payloadText = string.Empty;
if (e.ApplicationMessage.PayloadSegment.Count > 0)
{
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); Console.WriteLine($"+ Payload = {payloadText}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Console.WriteLine();
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks> <TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
+4 -4
View File
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
return new MqttApplicationMessage return new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes(Payload), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
} }
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
}; };
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };
+34 -30
View File
@@ -20,21 +20,25 @@ namespace MQTTnet.TestApp
{ {
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0 // TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters var unsafeTls13 = new MqttClientTlsOptions
{ {
UseTls = true, UseTls = true,
SslProtocol = SslProtocols.Tls13, SslProtocol = SslProtocols.Tls13,
// Don't use this in production code. This handler simply allows any invalid certificate to work. // Don't use this in production code. This handler simply allows any invalid certificate to work.
CertificateValidationHandler = w => true AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
}; };
#endif #endif
// Also defining TLS12 for servers that don't seem no to support TLS13. // Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters var unsafeTls12 = new MqttClientTlsOptions
{ {
UseTls = true, UseTls = true,
SslProtocol = SslProtocols.Tls12, SslProtocol = SslProtocols.Tls12,
// Don't use this in production code. This handler simply allows any invalid certificate to work. // Don't use this in production code. This handler simply allows any invalid certificate to work.
CertificateValidationHandler = w => true AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
}; };
// mqtt.eclipseprojects.io // mqtt.eclipseprojects.io
@@ -44,16 +48,16 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync( await ExecuteTestAsync(
"mqtt.eclipseprojects.io WS", "mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13", await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt") new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)", await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt") new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(), .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
true); true);
#endif #endif
@@ -68,12 +72,12 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync( await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12", "test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build()); new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
await ExecuteTestAsync("test.mosquitto.org TCP TLS13", await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
#endif #endif
await ExecuteTestAsync( await ExecuteTestAsync(
@@ -81,26 +85,26 @@ namespace MQTTnet.TestApp
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885) new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite") .WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311) .WithProtocolVersion(MqttProtocolVersion.V311)
.WithTls(unsafeTls12) .WithTlsOptions(unsafeTls12)
.Build()); .Build());
await ExecuteTestAsync( await ExecuteTestAsync(
"test.mosquitto.org WS", "test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync(
"test.mosquitto.org WS (WebSocket4Net)", "test.mosquitto.org WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(), new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true); true);
await ExecuteTestAsync( await ExecuteTestAsync(
"test.mosquitto.org WS TLS12", "test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
// await ExecuteTestAsync( await ExecuteTestAsync(
// "test.mosquitto.org WS TLS12 (WebSocket4Net)", "test.mosquitto.org WS TLS12 (WebSocket4Net)",
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(), new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
// true); true);
// broker.emqx.io // broker.emqx.io
await ExecuteTestAsync( await ExecuteTestAsync(
@@ -109,30 +113,30 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.emqx.io TCP TLS12", "broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build()); new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
await ExecuteTestAsync("broker.emqx.io TCP TLS13", await ExecuteTestAsync("broker.emqx.io TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883) new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
#endif #endif
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.emqx.io WS", "broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.emqx.io WS (WebSocket4Net)", "broker.emqx.io WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(), new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true); true);
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.emqx.io WS TLS12", "broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.emqx.io WS TLS12 (WebSocket4Net)", "broker.emqx.io WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(), new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
true); true);
// broker.hivemq.com // broker.hivemq.com
@@ -142,15 +146,14 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.hivemq.com WS", "broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync(
"broker.hivemq.com WS (WebSocket4Net)", "broker.hivemq.com WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(), new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true); true);
// mqtt.swifitch.cz: Does not seem to operate any more // mqtt.swifitch.cz: Does not seem to operate any more
// cloudmqtt.com: Cannot test because it does not offer a free plan any more. // cloudmqtt.com: Cannot test because it does not offer a free plan any more.
Write("Finished.", ConsoleColor.White); Write("Finished.", ConsoleColor.White);
@@ -197,9 +200,10 @@ namespace MQTTnet.TestApp
Write("[OK]\n", ConsoleColor.Green); Write("[OK]\n", ConsoleColor.Green);
} }
catch (Exception e) catch (Exception exception)
{ {
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red); Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red);
Write(exception + Environment.NewLine, ConsoleColor.Red);
} }
} }
+11 -4
View File
@@ -5,6 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
{ {
// Replace the payload with the timestamp. But also extending a JSON // Replace the payload with the timestamp. But also extending a JSON
// based payload with the timestamp is a suitable use case. // based payload with the timestamp is a suitable use case.
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
} }
if (e.ApplicationMessage.Topic == "not_allowed_topic") if (e.ApplicationMessage.Topic == "not_allowed_topic")
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
{ {
MqttNetConsoleLogger.PrintToConsole( var payloadText = string.Empty;
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", if (e.ApplicationMessage.PayloadSegment.Count > 0)
ConsoleColor.Magenta); {
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -13,8 +13,9 @@ using MQTTnet.Exceptions;
using MQTTnet.LowLevelClient; using MQTTnet.LowLevelClient;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{ {
[TestClass] [TestClass]
public sealed class LowLevelMqttClient_Tests : BaseTestClass public sealed class LowLevelMqttClient_Tests : BaseTestClass
@@ -17,72 +17,11 @@ using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
[TestClass] [TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
{ {
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod] [TestMethod]
public async Task Connect_To_Invalid_Server() public async Task Connect_To_Invalid_Server()
{ {
@@ -149,6 +88,38 @@ namespace MQTTnet.Tests.Client
} }
} }
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod] [TestMethod]
public async Task ManagedClients_Will_Message_Send() public async Task ManagedClients_Will_Message_Send()
{ {
@@ -164,7 +135,7 @@ namespace MQTTnet.Tests.Client
.Build(); .Build();
var dyingClient = testEnvironment.CreateClient(); var dyingClient = testEnvironment.CreateClient();
var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger); var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build()); await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
@@ -192,6 +163,67 @@ namespace MQTTnet.Tests.Client
} }
} }
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod] [TestMethod]
public async Task Start_Stop() public async Task Start_Stop()
{ {
@@ -199,7 +231,7 @@ namespace MQTTnet.Tests.Client
{ {
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger()); var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
@@ -226,7 +258,7 @@ namespace MQTTnet.Tests.Client
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger()); var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage(); var storage = new ManagedMqttClientTestStorage();
@@ -343,7 +375,7 @@ namespace MQTTnet.Tests.Client
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>(); var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient()); var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
managedClient.ApplicationMessageReceivedAsync += e => managedClient.ApplicationMessageReceivedAsync += e =>
{ {
@@ -371,7 +403,7 @@ namespace MQTTnet.Tests.Client
// Make sure that it gets received after subscribing again. // Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await LongTestDelay(); await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count); Assert.AreEqual(2, receivedManagedMessages.Count);
} }
} }
@@ -389,7 +421,7 @@ namespace MQTTnet.Tests.Client
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient(); var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
var subscribeTime = DateTime.UtcNow; var subscribeTime = DateTime.UtcNow;
@@ -422,7 +454,7 @@ namespace MQTTnet.Tests.Client
//wait a bit for the subscription to become established //wait a bit for the subscription to become established
await Task.Delay(500); await Task.Delay(500);
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
var messages = await SetupReceivingOfMessages(managedClient, 1); var messages = await SetupReceivingOfMessages(managedClient, 1);
@@ -439,7 +471,7 @@ namespace MQTTnet.Tests.Client
} }
} }
async Task<ManagedMqttClient> CreateManagedClientAsync( async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment, TestEnvironment testEnvironment,
IMqttClient underlyingClient = null, IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null, TimeSpan? connectionCheckInterval = null,
@@ -456,7 +488,7 @@ namespace MQTTnet.Tests.Client
// at connection check intervals // at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1); managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger()); var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
@@ -473,7 +505,7 @@ namespace MQTTnet.Tests.Client
/// name="managedClient" /> /// name="managedClient" />
/// has connected /// has connected
/// </summary> /// </summary>
Task GetConnectedTask(ManagedMqttClient managedClient) Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
{ {
var connected = new TaskCompletionSource<bool>(); var connected = new TaskCompletionSource<bool>();
@@ -495,7 +527,7 @@ namespace MQTTnet.Tests.Client
/// name="expectedNumberOfMessages" /> /// name="expectedNumberOfMessages" />
/// have been received /// have been received
/// </summary> /// </summary>
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages) Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
{ {
var receivedMessages = new List<MqttApplicationMessage>(); var receivedMessages = new List<MqttApplicationMessage>();
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>(); var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -0,0 +1,254 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Connection_Tests : BaseTestClass
{
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
}
}
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
public async Task ConnectTimeout_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
var disconnectHandlerCalled = false;
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
Assert.Fail("Must fail!");
}
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
}
await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
}
}
[TestMethod]
public async Task Disconnect_Clean()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
}
[TestMethod]
public async Task Disconnect_Clean_With_Custom_Reason()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
}
}
[TestMethod]
public async Task Disconnect_Clean_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count);
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
}
}
[TestMethod]
public async Task No_Unobserved_Exception()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var client = testEnvironment.CreateClient();
var options = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithTimeout(TimeSpan.FromSeconds(2)).Build();
try
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(0.5)))
{
await client.ConnectAsync(options, timeout.Token);
}
}
catch (OperationCanceledException)
{
}
client.Dispose();
// These delays and GC calls are required in order to make calling the finalizer reproducible.
GC.Collect();
GC.WaitForPendingFinalizers();
await LongTestDelay();
await LongTestDelay();
await LongTestDelay();
}
}
[TestMethod]
public async Task Return_Non_Success()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ResponseUserProperties = new List<MqttUserProperty>
{
new MqttUserProperty("Property", "Value")
};
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
Assert.IsNotNull(response);
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
Assert.AreEqual(response.UserProperties[0].Name, "Property");
Assert.AreEqual(response.UserProperties[0].Value, "Value");
}
}
[TestMethod]
public async Task Throw_Proper_Exception_When_Not_Connected()
{
try
{
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
}
}
catch (MqttCommunicationException exception)
{
if (exception.Message == "The client is not connected.")
{
return;
}
}
Assert.Fail();
}
}
}
@@ -17,11 +17,12 @@ using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients.MqttClient
{ {
[TestClass] [TestClass]
public sealed class MqttClient_Tests : BaseTestClass public sealed class MqttClient_Tests : BaseTestClass
@@ -71,60 +72,34 @@ namespace MQTTnet.Tests.Client
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttCommunicationException))] public async Task Connect_Disconnect_Connect()
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{ {
var client = new MqttFactory().CreateMqttClient(); using (var testEnvironment = CreateTestEnvironment())
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
}
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{ {
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token); await testEnvironment.StartServer();
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.DisconnectAsync();
await client.ConnectAsync(clientOptions);
} }
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttCommunicationException))] [ExpectedException(typeof(InvalidOperationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol() public async Task Connect_Multiple_Times_Should_Fail()
{ {
var client = new MqttFactory().CreateMqttClient(); using (var testEnvironment = CreateTestEnvironment())
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
public async Task ConnectTimeout_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{ {
var disconnectHandlerCalled = false; await testEnvironment.StartServer();
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build()); var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
Assert.Fail("Must fail!"); await client.ConnectAsync(clientOptions);
} await client.ConnectAsync(clientOptions);
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
}
await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
} }
} }
@@ -322,7 +297,7 @@ namespace MQTTnet.Tests.Client
Assert.IsNotNull(receivedMessage); Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic); Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(null, receivedMessage.Payload); Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
} }
} }
@@ -533,7 +508,7 @@ namespace MQTTnet.Tests.Client
client2.ApplicationMessageReceivedAsync += e => client2.ApplicationMessageReceivedAsync += e =>
{ {
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload)); client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -6,9 +6,8 @@ using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients
{ {
[TestClass] [TestClass]
public class MqttClientOptionsBuilder_Tests public class MqttClientOptionsBuilder_Tests
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
var packets = new List<string>(); var packets = new List<string>();
mqttClient.InspectPackage += eventArgs => mqttClient.InspectPacketAsync += eventArgs =>
{ {
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return CompletedTask.Instance; return CompletedTask.Instance;
-98
View File
@@ -1,98 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
namespace MQTTnet.Tests
{
[TestClass]
public class Extension_Tests
{
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfter()
{
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfterWithResult()
{
await MqttTaskTimeout.WaitAsync(
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[TestMethod]
public async Task TimeoutAfterWithInnerException()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterWithInnerExceptionWithResult()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
return iis[0];
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterMemoryUsage()
{
var initialMemory = GC.GetTotalMemory(true);
var tasks = Enumerable.Range(0, 100000)
.Select(i =>
{
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
TimeSpan.FromMinutes(1), CancellationToken.None);
});
await Task.WhenAll(tasks);
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
}
static void AssertIsLess(long bound, long actual)
{
if (bound < actual)
{
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
}
}
}
}
+57 -1
View File
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -45,6 +46,34 @@ namespace MQTTnet.Tests.Extensions
} }
} }
[TestMethod]
public async Task Execute_Success_Parameters_Propagated_Correctly()
{
var paramValue = "123";
var parameters = new Dictionary<string, object>
{
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" }
};
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}");
responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder()
.WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_0() public Task Execute_Success_With_QoS_0()
{ {
@@ -135,7 +164,7 @@ namespace MQTTnet.Tests.Extensions
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{ {
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
} }
} }
} }
@@ -222,5 +251,32 @@ namespace MQTTnet.Tests.Extensions
}; };
} }
} }
class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{
internal const string ExpectedParamName = "test_param_name";
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
if (context.Parameters == null)
{
throw new InvalidOperationException("Parameters dictionary expected to be not null");
}
if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue))
{
throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present");
}
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
}
}
} }
} }
@@ -23,7 +23,7 @@ namespace MQTTnet.Tests.Extensions
using (var client = factory.CreateMqttClient()) using (var client = factory.CreateMqttClient())
{ {
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build(); var options = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("ws://a.b/mqtt")).WithTimeout(TimeSpan.FromSeconds(2)).Build();
await client.ConnectAsync(options).ConfigureAwait(false); await client.ConnectAsync(options).ConfigureAwait(false);
} }
} }
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
var publishPacket = new MqttPublishPacket var publishPacket = new MqttPublishPacket
{ {
Topic = "abcdefghijklmnopqrstuvwxyz0123456789", Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
Payload = payload PayloadSegment = new ArraySegment<byte>(payload)
}; };
var serializationHelper = new MqttPacketSerializationHelper(); var serializationHelper = new MqttPacketSerializationHelper();
var buffer = serializationHelper.Encode(publishPacket); var buffer = serializationHelper.Encode(publishPacket);
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNotNull(publishPacketCopy); Assert.IsNotNull(publishPacketCopy);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
// Now modify the payload and test again. // Now modify the payload and test again.
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT"); publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
buffer = serializationHelper.Encode(publishPacket); buffer = serializationHelper.Encode(publishPacket);
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
Assert.IsNotNull(publishPacketCopy2); Assert.IsNotNull(publishPacketCopy2);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
} }
[TestMethod] [TestMethod]
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
using (var adapter = new MqttChannelAdapter( using (var adapter = new MqttChannelAdapter(
channel, channel,
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
null,
new MqttNetEventLogger())) new MqttNetEventLogger()))
{ {
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer) MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
{ {
var channel = new MemoryMqttChannel(buffer); var channel = new MemoryMqttChannel(buffer);
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
return packetFormatterAdapter.ProtocolVersion; return packetFormatterAdapter.ProtocolVersion;
} }
MqttBufferReader ReaderFactory(byte[] data)
{
var reader = new MqttBufferReader();
reader.SetBuffer(data, 0, data.Length);
return reader;
}
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
{ {
var writer = bufferWriter ?? WriterFactory(); var writer = bufferWriter ?? WriterFactory();
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray())) using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
{ {
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -76,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
} }
[TestMethod] [TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310() public void Serialize_Full_MqttConnAckPacket_V310()
{ {
var connAckPacket = new MqttConnAckPacket var connAckPacket = new MqttConnAckPacket
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
@@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter namespace MQTTnet.Tests.Formatter
@@ -47,23 +48,32 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(1234U, reader.ReadVariableByteInteger()); Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
Assert.AreEqual(9876U, reader.ReadVariableByteInteger()); Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
} }
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Throw_If_String_Too_Long()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString(string.Empty.PadLeft(65536));
}
[TestMethod] [TestMethod]
public void Write_And_Read_Multiple_Times() public void Write_And_Read_Multiple_Times()
{ {
var writer = new MqttBufferWriter(4096, 65535); var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinaryData(new byte[1234]); writer.WriteBinary(new byte[1234]);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(new byte[48]); writer.WriteBinary(new byte[48]);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(new byte[3]); writer.WriteBinary(new byte[3]);
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
+3 -2
View File
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net48</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
+1 -1
View File
@@ -273,7 +273,7 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
CollectionAssert.AreEqual(applicationMessage.Payload, receivedMessage.Payload); CollectionAssert.AreEqual(applicationMessage.PayloadSegment.ToArray(), receivedMessage.PayloadSegment.ToArray());
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
} }
} }
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.MQTTv5 namespace MQTTnet.Tests.MQTTv5
{ {
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
public void AssertReceivedCountEquals(int expectedCount) public void AssertReceivedCountEquals(int expectedCount)
{ {
lock (_receivedEventArgs) Assert.AreEqual(expectedCount, Count);
{
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
}
} }
public string GeneratePayloadSequence() public string GeneratePayloadSequence()
+121 -63
View File
@@ -25,7 +25,6 @@ namespace MQTTnet.Tests.Mockups
{ {
readonly List<string> _clientErrors = new List<string>(); readonly List<string> _clientErrors = new List<string>();
readonly List<IMqttClient> _clients = new List<IMqttClient>(); readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>(); readonly List<Exception> _exceptions = new List<Exception>();
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>(); readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly MqttProtocolVersion _protocolVersion; readonly MqttProtocolVersion _protocolVersion;
@@ -40,6 +39,8 @@ namespace MQTTnet.Tests.Mockups
_protocolVersion = protocolVersion; _protocolVersion = protocolVersion;
TestContext = testContext; TestContext = testContext;
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
ServerLogger.LogMessagePublished += (s, e) => ServerLogger.LogMessagePublished += (s, e) =>
{ {
if (Debugger.IsAttached) if (Debugger.IsAttached)
@@ -65,9 +66,12 @@ namespace MQTTnet.Tests.Mockups
if (e.LogMessage.Level == MqttNetLogLevel.Error) if (e.LogMessage.Level == MqttNetLogLevel.Error)
{ {
lock (_clientErrors) if (!IgnoreClientLogErrors)
{ {
_clientErrors.Add(e.LogMessage.ToString()); lock (_clientErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
}
} }
} }
}; };
@@ -212,41 +216,56 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient() public IMqttClient CreateClient()
{ {
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e =>
{
if (TestContext != null)
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
lock (_clients) lock (_clients)
{ {
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
var client = Factory.CreateMqttClient(logger);
_clients.Add(client); _clients.Add(client);
client.ConnectingAsync += e =>
{
if (TestContext != null)
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
return client;
} }
return client;
}
public MqttClientOptions CreateDefaultClientOptions()
{
return CreateDefaultClientOptionsBuilder().Build();
}
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{
return Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(_protocolVersion)
.WithTcpServer("127.0.0.1", ServerPort)
.WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
} }
public ILowLevelMqttClient CreateLowLevelClient() public ILowLevelMqttClient CreateLowLevelClient()
{ {
lock (_clients) var client = Factory.CreateLowLevelMqttClient(ClientLogger);
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
return client; lock (_lowLevelClients)
{
_lowLevelClients.Add(client);
} }
return client;
} }
public MqttServer CreateServer(MqttServerOptions options) public MqttServer CreateServer(MqttServerOptions options)
@@ -280,45 +299,68 @@ namespace MQTTnet.Tests.Mockups
public void Dispose() public void Dispose()
{ {
foreach (var mqttClient in _clients) try
{ {
lock (_clients)
{
foreach (var mqttClient in _clients)
{
try
{
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already disconnected the client.
}
finally
{
mqttClient?.Dispose();
}
}
_clients.Clear();
}
lock (_lowLevelClients)
{
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
}
_lowLevelClients.Clear();
}
try try
{ {
//mqttClient.DisconnectAsync().GetAwaiter().GetResult(); Server?.StopAsync().GetAwaiter().GetResult();
} }
catch catch
{ {
// This can happen when the test already disconnected the client. // This can happen when the test already stopped the server.
} }
finally finally
{ {
mqttClient?.Dispose(); Server?.Dispose();
} }
}
foreach (var lowLevelMqttClient in _lowLevelClients) Server = null;
{
lowLevelMqttClient.Dispose();
}
try ThrowIfLogErrors();
{
Server?.StopAsync().GetAwaiter().GetResult(); GC.Collect();
} GC.WaitForFullGCComplete();
catch GC.WaitForPendingFinalizers();
{
// This can happen when the test already stopped the server. if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
}
} }
finally finally
{ {
Server?.Dispose(); TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException;
}
ThrowIfLogErrors();
if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
} }
} }
@@ -335,7 +377,7 @@ namespace MQTTnet.Tests.Mockups
var options = optionsBuilder.Build(); var options = optionsBuilder.Build();
var server = CreateServer(options); var server = CreateServer(options);
await server.StartAsync(); await server.StartAsync().ConfigureAwait(false);
// The OS has chosen the port to we have to properly expose it to the tests. // The OS has chosen the port to we have to properly expose it to the tests.
ServerPort = options.DefaultEndpointOptions.Port; ServerPort = options.DefaultEndpointOptions.Port;
@@ -363,33 +405,49 @@ namespace MQTTnet.Tests.Mockups
public void ThrowIfLogErrors() public void ThrowIfLogErrors()
{ {
lock (_serverErrors) if (!IgnoreServerLogErrors)
{ {
if (!IgnoreServerLogErrors && _serverErrors.Count > 0) lock (_serverErrors)
{ {
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."; if (_serverErrors.Count > 0)
Console.WriteLine(message); {
throw new Exception(message); var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
Console.WriteLine(message);
throw new Exception(message);
}
} }
} }
lock (_clientErrors) if (!IgnoreClientLogErrors)
{ {
if (!IgnoreClientLogErrors && _clientErrors.Count > 0) lock (_clientErrors)
{ {
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})"; if (_clientErrors.Count > 0)
Console.WriteLine(message); {
throw new Exception(message); var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
}
} }
} }
} }
public void TrackException(Exception exception) public void TrackException(Exception exception)
{ {
if (exception == null)
{
return;
}
lock (_exceptions) lock (_exceptions)
{ {
_exceptions.Add(exception); _exceptions.Add(exception);
} }
} }
void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
TrackException(e.Exception);
}
} }
} }
@@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -11,7 +12,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public class MqttApplicationMessageBuilder_Tests public sealed class MqttApplicationMessageBuilder_Tests
{ {
[TestMethod] [TestMethod]
public void CreateApplicationMessage_TopicOnly() public void CreateApplicationMessage_TopicOnly()
@@ -29,7 +30,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("xyz", message.Topic); Assert.AreEqual("xyz", message.Topic);
Assert.IsFalse(message.Retain); Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00");
} }
[TestMethod] [TestMethod]
@@ -41,7 +42,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("123", message.Topic); Assert.AreEqual("123", message.Topic);
Assert.IsFalse(message.Retain); Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello"); Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
} }
[TestMethod] [TestMethod]
@@ -0,0 +1,45 @@
#pragma warning disable CS0618 // Type or member is obsolete
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttApplicationMessageTest
{
[TestMethod]
public void PayloadSegment()
{
var message = new MqttApplicationMessage();
Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsFalse(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
}
}
}
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttApplicationMessageValidator_Tests
{
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Succeed_When_Using_TopicAlias_And_MQTT_311()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311);
}
[TestMethod]
public void Succeed_When_Using_TopicAlias_And_MQTT_500()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500);
}
[TestMethod]
public void Succeed_When_Using_UserProperties_And_MQTT_500()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(
new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(),
MqttProtocolVersion.V500);
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
{
MqttApplicationMessageValidator.ThrowIfNotSupported(
new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(),
MqttProtocolVersion.V311);
}
}
}
@@ -0,0 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttClientOptionsValidator_Tests
{
[TestMethod]
public void Succeed_When_Using_UserProperties_And_MQTT_500()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
public void Succeed_When_Using_WillUserProperties_And_MQTT_500()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Throw_When_Using_UserProperties_And_MQTT_311()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
}
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
}
[TestMethod]
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500()
{
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
}
}
}
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
{ {
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535)); var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance); var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
writer.WriteString("1234567890"); writer.WriteString("1234567890");
Assert.AreEqual(10 + 2, writer.Length); Assert.AreEqual(10 + 2, writer.Length);
writer.WriteBinaryData(new byte[300]); writer.WriteBinary(new byte[300]);
Assert.AreEqual(300 + 2 + 12, writer.Length); Assert.AreEqual(300 + 2 + 12, writer.Length);
writer.WriteBinaryData(new byte[5000]); writer.WriteBinary(new byte[5000]);
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length); Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
} }
} }
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public class MqttTopicValidator_Tests public sealed class MqttTopicValidator_Tests
{ {
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))] [ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Plus() public void Invalid_Topic_Empty()
{ {
MqttTopicValidator.ThrowIfInvalid("/a/+/c"); MqttTopicValidator.ThrowIfInvalid(string.Empty);
} }
[TestMethod] [TestMethod]
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))] [ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Empty() public void Invalid_Topic_Plus()
{ {
MqttTopicValidator.ThrowIfInvalid(string.Empty); MqttTopicValidator.ThrowIfInvalid("/a/+/c");
}
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
} }
} }
} }
+15 -18
View File
@@ -303,7 +303,7 @@ namespace MQTTnet.Tests.Server
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended"); e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("extended"));
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -314,7 +314,7 @@ namespace MQTTnet.Tests.Server
var isIntercepted = false; var isIntercepted = false;
c2.ApplicationMessageReceivedAsync += e => c2.ApplicationMessageReceivedAsync += e =>
{ {
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()), StringComparison.Ordinal) == 0;
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -425,7 +425,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage new MqttApplicationMessage
{ {
Topic = "/test/1", Topic = "/test/1",
Payload = Encoding.UTF8.GetBytes("true"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("true")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
}) })
{ {
@@ -598,8 +598,7 @@ namespace MQTTnet.Tests.Server
// c // c
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder); var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await Task.Delay(500); await LongTestDelay();
var flow = string.Join(string.Empty, events); var flow = string.Join(string.Empty, events);
Assert.AreEqual("c", flow); Assert.AreEqual("c", flow);
@@ -607,10 +606,8 @@ namespace MQTTnet.Tests.Server
// Connect client with same client ID. Should disconnect existing client. // Connect client with same client ID. Should disconnect existing client.
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder); var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await Task.Delay(500); await LongTestDelay();
flow = string.Join(string.Empty, events); flow = string.Join(string.Empty, events);
Assert.AreEqual("cdc", flow); Assert.AreEqual("cdc", flow);
c2.ApplicationMessageReceivedAsync += e => c2.ApplicationMessageReceivedAsync += e =>
@@ -628,27 +625,27 @@ namespace MQTTnet.Tests.Server
// r // r
await c2.PublishStringAsync("topic"); await c2.PublishStringAsync("topic");
await Task.Delay(500); await LongTestDelay();
flow = string.Join(string.Empty, events); flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcr", flow); Assert.AreEqual("cdcr", flow);
// nothing // nothing
Assert.AreEqual(false, c1.IsConnected); Assert.AreEqual(false, c1.IsConnected);
await c1.DisconnectAsync(); await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected); Assert.AreEqual(false, c1.IsConnected);
await Task.Delay(500); await LongTestDelay();
// d // d
Assert.AreEqual(true, c2.IsConnected); Assert.AreEqual(true, c2.IsConnected);
await c2.DisconnectAsync(); await c2.DisconnectAsync();
await Task.Delay(500); await LongTestDelay();
await server.StopAsync(); await server.StopAsync();
await LongTestDelay();
flow = string.Join(string.Empty, events); flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcrd", flow); Assert.AreEqual("cdcrd", flow);
} }
@@ -783,7 +780,7 @@ namespace MQTTnet.Tests.Server
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
client1.ApplicationMessageReceivedAsync += e => client1.ApplicationMessageReceivedAsync += e =>
{ {
receivedBody = e.ApplicationMessage.Payload; receivedBody = e.ApplicationMessage.PayloadSegment.ToArray();
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -852,11 +849,11 @@ namespace MQTTnet.Tests.Server
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
await Task.Delay(100); // Stopping the server should disconnect the connection with the client
// which, it turn, will fire the disconnected event at the client.
await server.StopAsync(); await server.StopAsync();
await Task.Delay(100); await LongTestDelay();
Assert.AreEqual(1, disconnectCalled); Assert.AreEqual(1, disconnectCalled);
} }
@@ -954,7 +951,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient(); var client = testEnvironment.CreateClient();
client.InspectPackage += e => client.InspectPacketAsync += e =>
{ {
if (e.Buffer.Length > 0) if (e.Buffer.Length > 0)
{ {
@@ -0,0 +1,426 @@
#if !(NET452 || NET461 || NET48)
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Certificates;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
// missing certificate builder api means tests won't work for older frameworks
[TestClass]
public sealed class HotSwapCerts_Tests
{
static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
[TestMethod]
public async Task ClientCertChangeWithoutServerUpdateFailsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
client01.HotSwapClientCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
client01.WaitForConnectToFail(DefaultTimeout);
}
}
[TestMethod]
public async Task ClientCertChangeWithServerUpdateAcceptsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
client01.HotSwapClientCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.WaitForConnectOrFail(DefaultTimeout);
}
}
[TestMethod]
public async Task ServerCertChangeWithClientCertUpdateAllowsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
server.HotSwapServerCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
client01.InstallNewServerCert(server.GetCurrentServerCert());
client01.WaitForConnectOrFail(DefaultTimeout);
}
}
[TestMethod]
public async Task ServerCertChangeWithoutClientCertUpdateFailsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
server.HotSwapServerCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
client01.WaitForConnectToFail(DefaultTimeout);
}
}
static X509Certificate2 CreateSelfSignedCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
sealed class ClientTestHarness : IDisposable
{
readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider();
IMqttClient _client;
public string ClientId => _client.Options.ClientId;
public Task Connect()
{
return Run_Client_Connection();
}
public void Dispose()
{
_client.Dispose();
_hotSwapClient.Dispose();
}
public X509Certificate2 GetCurrentClientCert()
{
var result = _hotSwapClient.GetCertificates()[0];
return new X509Certificate2(result);
}
public void HotSwapClientCert()
{
_hotSwapClient.HotSwapCert();
}
public void InstallNewServerCert(X509Certificate2 serverCert)
{
_hotSwapClient.InstallNewServerCert(serverCert);
}
public void WaitForConnectOrFail(TimeSpan timeout)
{
Thread.Sleep(100);
if (!_client.IsConnected)
{
_client.ReconnectAsync().Wait(timeout);
}
WaitForConnect(timeout);
Assert.IsNotNull(_client, "Client was never initialized");
Assert.IsTrue(_client.IsConnected, $"Client connection failed after {timeout}");
}
public void WaitForConnectToFail(TimeSpan timeout)
{
Assert.IsFalse(_client.IsConnected, "Client should be disconnected before waiting for connect.");
WaitForConnect(timeout);
Assert.IsNotNull(_client, "Client was never initialized");
Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail");
}
void WaitForDisconnect(TimeSpan timeout)
{
var timer = Stopwatch.StartNew();
while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout)
{
Thread.Sleep(100);
}
}
public void WaitForDisconnectOrFail(TimeSpan timeout)
{
WaitForDisconnect(timeout);
Assert.IsNotNull(_client, "Client was never initialized");
Assert.IsFalse(_client.IsConnected, $"Client connection should have disconnected after {timeout}");
}
async Task Run_Client_Connection()
{
var optionsBuilder = new MqttClientOptionsBuilder()
.WithTlsOptions(
o => o.WithClientCertificatesProvider(_hotSwapClient)
.WithCertificateValidationHandler(_hotSwapClient.OnCertificateValidation)
.WithSslProtocols(SslProtocols.Tls12))
.WithTcpServer("localhost")
.WithCleanSession()
.WithProtocolVersion(MqttProtocolVersion.V500);
var mqttClientOptions = optionsBuilder.Build();
var factory = new MqttFactory();
var mqttClient = factory.CreateMqttClient();
_client = mqttClient;
await mqttClient.ConnectAsync(mqttClientOptions);
}
void WaitForConnect(TimeSpan timeout)
{
var timer = Stopwatch.StartNew();
while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout)
{
Thread.Sleep(100);
}
}
}
sealed class ServerTestHarness : IDisposable
{
readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider();
MqttServer _server;
public void Dispose()
{
if (_server != null)
{
_server.StopAsync().Wait();
_server.Dispose();
}
_hotSwapServer?.Dispose();
}
public Task ForceDisconnectAsync(ClientTestHarness client)
{
return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError);
}
public X509Certificate2 GetCurrentServerCert()
{
return _hotSwapServer.GetCertificate();
}
public void HotSwapServerCert()
{
_hotSwapServer.HotSwapCert();
}
public void InstallNewClientCert(X509Certificate2 serverCert)
{
_hotSwapServer.InstallNewClientCert(serverCert);
}
public Task StartServer()
{
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(_hotSwapServer)
.WithRemoteCertificateValidationCallback(_hotSwapServer.RemoteCertificateValidationCallback)
.WithEncryptedEndpoint()
.Build();
mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true;
_server = mqttFactory.CreateMqttServer(mqttServerOptions);
return _server.StartAsync();
}
}
class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable
{
X509Certificate2Collection _certificates;
ConcurrentBag<X509Certificate2> _serverCerts = new ConcurrentBag<X509Certificate2>();
public HotSwappableClientCertProvider()
{
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
}
public void Dispose()
{
if (_certificates != null)
{
foreach (var certs in _certificates)
{
#if !NET452
certs.Dispose();
#endif
}
}
}
public X509CertificateCollection GetCertificates()
{
return new X509Certificate2Collection(_certificates);
}
public void HotSwapCert()
{
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
}
public void InstallNewServerCert(X509Certificate2 serverCert)
{
_serverCerts.Add(serverCert);
}
public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext)
{
var serverCerts = _serverCerts.ToArray();
var providedCert = certContext.Certificate.GetRawCertData();
for (int i = 0, n = serverCerts.Length; i < n; i++)
{
var currentcert = serverCerts[i];
if (currentcert.RawData.SequenceEqual(providedCert))
{
return true;
}
}
return false;
}
}
sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable
{
readonly ConcurrentBag<X509Certificate2> _clientCerts = new ConcurrentBag<X509Certificate2>();
X509Certificate2 _certificate;
public HotSwappableServerCertProvider()
{
_certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
}
public void Dispose()
{
#if !NET452
_certificate.Dispose();
#endif
}
public X509Certificate2 GetCertificate()
{
return _certificate;
}
public void HotSwapCert()
{
var newCert = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
var oldCert = Interlocked.Exchange(ref _certificate, newCert);
#if !NET452
oldCert.Dispose();
#endif
}
public void InstallNewClientCert(X509Certificate2 certificate)
{
_clientCerts.Add(certificate);
}
public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
var serverCerts = _clientCerts.ToArray();
var providedCert = certificate.GetRawCertData();
for (int i = 0, n = serverCerts.Length; i < n; i++)
{
var currentCert = serverCerts[i];
if (currentCert.RawData.SequenceEqual(providedCert))
{
return true;
}
}
return false;
}
}
}
}
#endif
@@ -0,0 +1,25 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class MqttRetainedMessageManager_Tests
{
[TestMethod]
public async Task MqttRetainedMessageManager_GetUndefinedTopic()
{
var logger = new Mockups.TestLogger();
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger);
var task = retainedMessagesManager.GetMessage("undefined");
Assert.IsNotNull(task, "Task should not be null");
var result = await task;
Assert.IsNull(result, "Null result expected");
}
}
}
@@ -221,14 +221,17 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer(); var eventContainer = new MqttServerEventContainer();
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger); var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager); var session = new MqttSession(new MqttConnectPacket
{
ClientId = ""
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager); _subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
} }
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _); MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId); return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
} }
} }
@@ -1,58 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Publish_Tests : BaseTestClass
{
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
}
}
@@ -5,7 +5,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
{ {
@@ -27,5 +30,118 @@ namespace MQTTnet.Tests.Server
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
} }
} }
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Intercept_Client_Enqueue()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
server.InterceptingClientEnqueueAsync += e =>
{
e.AcceptEnqueue = false;
return CompletedTask.Instance;
};
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
// Do not increase because the internal enqueue to the target client is not accepted!
receivedMessages.AssertReceivedCountEquals(1);
}
}
[TestMethod]
public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One"));
await receiverOne.SubscribeAsync("A");
var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne);
var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two"));
await receiverTwo.SubscribeAsync("A");
var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo);
var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three"));
await receiverThree.SubscribeAsync("A");
var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree);
server.InterceptingClientEnqueueAsync += e =>
{
if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false;
return CompletedTask.Instance;
};
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
receivedMessagesOne.AssertReceivedCountEquals(1);
receivedMessagesTwo.AssertReceivedCountEquals(0);
receivedMessagesThree.AssertReceivedCountEquals(1);
}
}
} }
} }
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Server
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)); var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode); Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
} }
} }
+30 -12
View File
@@ -70,6 +70,28 @@ namespace MQTTnet.Tests.Server
} }
} }
[TestMethod]
public async Task Do_Not_Use_Expired_Session()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3));
// Kill the client connection and ensure that the session will stay there but is expired after 3 seconds!
c1.Dispose();
await Task.Delay(TimeSpan.FromSeconds(6));
c1 = testEnvironment.CreateClient();
var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build();
var connectResult = await c1.ConnectAsync(options);
Assert.AreEqual(false, connectResult.IsSessionPresent);
}
}
[TestMethod] [TestMethod]
public async Task Fire_Deleted_Event() public async Task Fire_Deleted_Event()
{ {
@@ -121,7 +143,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, session.Items["can_subscribe_x"]); Assert.AreEqual(true, session.Items["can_subscribe_x"]);
} }
} }
[TestMethod] [TestMethod]
[DataRow(MqttProtocolVersion.V310)] [DataRow(MqttProtocolVersion.V310)]
[DataRow(MqttProtocolVersion.V311)] [DataRow(MqttProtocolVersion.V311)]
@@ -134,16 +156,12 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion); var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
var hasReceive = false; var hasReceive = false;
void OnReceive() void OnReceive()
{ {
if (!hasReceive) hasReceive = true;
{
hasReceive = true;
}
} }
// Try to connect 50 clients at the same time. // Try to connect 50 clients at the same time.
@@ -152,7 +170,7 @@ namespace MQTTnet.Tests.Server
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList(); var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay(); await LongTestDelay();
Assert.AreEqual(1, connectedClients.Count); Assert.AreEqual(1, connectedClients.Count);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10)); var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
@@ -164,7 +182,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive); Assert.AreEqual(true, hasReceive);
} }
} }
[DataTestMethod] [DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)] [DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
@@ -265,7 +283,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty); e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty));
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -355,8 +373,8 @@ namespace MQTTnet.Tests.Server
onReceive(); onReceive();
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{ {
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false); await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
} }
+25 -2
View File
@@ -62,7 +62,7 @@ namespace MQTTnet.Tests.Server
} }
} }
} }
[TestMethod] [TestMethod]
public async Task Deny_Invalid_Topic() public async Task Deny_Invalid_Topic()
{ {
@@ -90,6 +90,29 @@ namespace MQTTnet.Tests.Server
} }
} }
[TestMethod]
public async Task Intercept_Subscribe_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
InterceptingSubscriptionEventArgs eventArgs = null;
server.InterceptingSubscriptionAsync += e =>
{
eventArgs = e;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
await client.SubscribeAsync(subscribeOptions);
CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
}
}
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))] [ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Subscribing() public async Task Disconnect_While_Subscribing()
@@ -105,7 +128,7 @@ namespace MQTTnet.Tests.Server
await client.SubscribeAsync("#"); await client.SubscribeAsync("#");
} }
} }
[TestMethod] [TestMethod]
public async Task Enqueue_Message_After_Subscription() public async Task Enqueue_Message_After_Subscription()
{ {
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
var topics = t.Value; var topics = t.Value;
foreach (var topic in topics) foreach (var topic in topics)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
bucketDepths.TryGetValue(topicHash, out var currentValue); bucketDepths.TryGetValue(topicHash, out var currentValue);
++currentValue; ++currentValue;
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
{ {
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#"; var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
} }
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
{ {
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+"; var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
} }
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
// UInt64 is limited to 8 levels // UInt64 is limited to 8 levels
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString(); var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present"); Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString(); var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
var l3 = "#"; var l3 = "#";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1"; var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present"); Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1"; var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask) void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Topic: " + topic); Console.WriteLine("Topic: " + topic);
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
{ {
var topicToFind = topicsToFind[countDown]; var topicToFind = topicsToFind[countDown];
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _); MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result); clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
if (result.IsSubscribed) if (result.IsSubscribed)
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer(); var eventContainer = new MqttServerEventContainer();
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger); var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger); var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager); _clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
List<string> topicsToSubscribe; List<string> topicsToSubscribe;
+202
View File
@@ -0,0 +1,202 @@
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
using System;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Certificates;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Tls_Tests : BaseTestClass
{
static X509Certificate2 CreateCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
[TestMethod]
public async Task Tls_Swap_Test()
{
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
var firstOid = "1.3.6.1.5.5.7.3.1";
var secondOid = "1.3.6.1.5.5.7.3.2";
var certificateProvider = new CertificateProvider
{
CurrentCertificate = CreateCertificate(firstOid)
};
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
var serverOptions = serverOptionsBuilder.Build();
var server = testEnvironment.CreateServer(serverOptions);
var publishedCount = 0;
server.InterceptingPublishAsync += args =>
{
Interlocked.Increment(ref publishedCount);
return Task.CompletedTask;
};
await server.StartAsync();
var firstClient = await ConnectClientAsync(
testEnvironment,
args =>
{
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
return true;
});
var firstClientReceivedCount = 0;
firstClient.ApplicationMessageReceivedAsync += args =>
{
Interlocked.Increment(ref firstClientReceivedCount);
return Task.CompletedTask;
};
await firstClient.SubscribeAsync("TestTopic1");
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
}));
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
// Validate that the certificate was switched
var secondClient = await ConnectClientAsync(
testEnvironment,
args =>
{
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
return true;
});
var secondClientReceivedCount = 0;
secondClient.ApplicationMessageReceivedAsync += args =>
{
Interlocked.Increment(ref secondClientReceivedCount);
return Task.CompletedTask;
};
await secondClient.SubscribeAsync("TestTopic2");
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic2",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic2",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
}));
// Ensure first client still works
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
}));
await Task.Delay(1000);
Assert.AreEqual(6, publishedCount);
Assert.AreEqual(4, firstClientReceivedCount);
Assert.AreEqual(2, secondClientReceivedCount);
await server.StopAsync();
}
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
{
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer("localhost", 8883)
.WithTlsOptions(
o =>
{
o.WithSslProtocols(SslProtocols.Tls12).WithCertificateValidationHandler(certValidator);
});
var clientOptions = clientOptionsBuilder.Build();
return await testEnvironment.ConnectClient(clientOptions);
}
sealed class CertificateProvider : ICertificateProvider
{
public X509Certificate2 CurrentCertificate { get; set; }
public X509Certificate2 GetCertificate()
{
return CurrentCertificate;
}
}
}
}
#endif
@@ -2,10 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
{ {
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
await client.UnsubscribeAsync("#"); await client.UnsubscribeAsync("#");
} }
} }
[TestMethod]
public async Task Intercept_Unsubscribe_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
InterceptingUnsubscriptionEventArgs eventArgs = null;
server.InterceptingUnsubscriptionAsync += e =>
{
eventArgs = e;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var unsubscribeOptions = testEnvironment.Factory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
await client.UnsubscribeAsync(unsubscribeOptions);
CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
}
}
} }
} }
@@ -150,18 +150,8 @@ namespace MQTTnet.Tests
{ {
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter)); Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
ulong topicHash; MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
ulong topicHashMask; MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
bool topicHasWildcard;
MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
ulong filterTopicHash;
ulong filterTopicHashMask;
bool filterTopicHasWildcard;
MqttSubscription.CalculateTopicHash(filter, out filterTopicHash, out filterTopicHashMask, out filterTopicHasWildcard);
if (expectedResult == MqttTopicFilterCompareResult.IsMatch) if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
{ {
+55 -28
View File
@@ -26,20 +26,14 @@ namespace MQTTnet.Adapter
readonly IMqttChannel _channel; readonly IMqttChannel _channel;
readonly byte[] _fixedHeaderBuffer = new byte[2]; readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly MqttPacketInspector _packetInspector;
readonly byte[] _singleByteBuffer = new byte[1]; readonly byte[] _singleByteBuffer = new byte[1];
readonly AsyncLock _syncRoot = new AsyncLock(); readonly AsyncLock _syncRoot = new AsyncLock();
long _bytesReceived; Statistics _statistics; // mutable struct, don't make readonly!
long _bytesSent;
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger) public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger)
{ {
_channel = channel ?? throw new ArgumentNullException(nameof(channel)); _channel = channel ?? throw new ArgumentNullException(nameof(channel));
_packetInspector = packetInspector;
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
@@ -51,9 +45,11 @@ namespace MQTTnet.Adapter
_logger = logger.WithSource(nameof(MqttChannelAdapter)); _logger = logger.WithSource(nameof(MqttChannelAdapter));
} }
public long BytesReceived => Interlocked.Read(ref _bytesReceived); public bool AllowPacketFragmentation { get; set; } = true;
public long BytesSent => Interlocked.Read(ref _bytesSent); public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
public X509Certificate2 ClientCertificate => _channel.ClientCertificate; public X509Certificate2 ClientCertificate => _channel.ClientCertificate;
@@ -65,6 +61,8 @@ namespace MQTTnet.Adapter
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public MqttPacketInspector PacketInspector { get; set; }
public async Task ConnectAsync(CancellationToken cancellationToken) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -78,12 +76,30 @@ namespace MQTTnet.Adapter
* block forever. Even a cancellation token is not supported properly. * block forever. Even a cancellation token is not supported properly.
*/ */
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>(); var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null))) using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{ {
var connectTask = Task.Run(
async () =>
{
try
{
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// If the timeout is already reached the exception is no longer of interest and
// must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException.
if (!timeout.Task.IsCompleted)
{
throw;
}
}
},
CancellationToken.None);
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false); await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted) if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{ {
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken); throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
@@ -127,7 +143,7 @@ namespace MQTTnet.Adapter
try try
{ {
_packetInspector?.BeginReceivePacket(); PacketInspector?.BeginReceivePacket();
ReceivedMqttPacket receivedPacket; ReceivedMqttPacket receivedPacket;
var receivedPacketTask = ReceiveAsync(cancellationToken); var receivedPacketTask = ReceiveAsync(cancellationToken);
@@ -145,9 +161,9 @@ namespace MQTTnet.Adapter
return null; return null;
} }
_packetInspector?.EndReceivePacket(); PacketInspector?.EndReceivePacket();
Interlocked.Add(ref _bytesSent, receivedPacket.TotalLength); Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown) if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
{ {
@@ -183,8 +199,7 @@ namespace MQTTnet.Adapter
public void ResetStatistics() public void ResetStatistics()
{ {
Interlocked.Exchange(ref _bytesReceived, 0L); _statistics.Reset();
Interlocked.Exchange(ref _bytesSent, 0L);
} }
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
@@ -202,21 +217,21 @@ namespace MQTTnet.Adapter
try try
{ {
var packetBuffer = PacketFormatterAdapter.Encode(packet); var packetBuffer = PacketFormatterAdapter.Encode(packet);
_packetInspector?.BeginSendPacket(packetBuffer); PacketInspector?.BeginSendPacket(packetBuffer);
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
if (packetBuffer.Payload.Count > 0) if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
{
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
}
else
{ {
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
} }
else
{
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
}
Interlocked.Add(ref _bytesReceived, packetBuffer.Length); Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -275,7 +290,7 @@ namespace MQTTnet.Adapter
return 0; return 0;
} }
_packetInspector?.FillReceiveBuffer(_singleByteBuffer); PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
encodedByte = _singleByteBuffer[0]; encodedByte = _singleByteBuffer[0];
@@ -314,7 +329,7 @@ namespace MQTTnet.Adapter
{ {
return ReadFixedHeaderResult.ConnectionClosed; return ReadFixedHeaderResult.ConnectionClosed;
} }
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return ReadFixedHeaderResult.Canceled; return ReadFixedHeaderResult.Canceled;
@@ -328,7 +343,7 @@ namespace MQTTnet.Adapter
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
_packetInspector?.FillReceiveBuffer(buffer); PacketInspector?.FillReceiveBuffer(buffer);
var hasRemainingLength = buffer[1] != 0; var hasRemainingLength = buffer[1] != 0;
if (!hasRemainingLength) if (!hasRemainingLength)
@@ -412,7 +427,7 @@ namespace MQTTnet.Adapter
bodyOffset += readBytes; bodyOffset += readBytes;
} while (bodyOffset < bodyLength); } while (bodyOffset < bodyLength);
_packetInspector?.FillReceiveBuffer(body); PacketInspector?.FillReceiveBuffer(body);
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength); var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength); return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
@@ -459,5 +474,17 @@ namespace MQTTnet.Adapter
throw new MqttCommunicationException(exception); throw new MqttCommunicationException(exception);
} }
struct Statistics
{
public long _bytesReceived;
public long _bytesSent;
public void Reset()
{
Volatile.Write(ref _bytesReceived, 0);
Volatile.Write(ref _bytesSent, 0);
}
}
} }
} }
@@ -82,12 +82,7 @@ namespace MQTTnet.Adapter
{ {
try try
{ {
var eventArgs = new InspectMqttPacketEventArgs var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
{
Buffer = buffer,
Direction = direction
};
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult(); _asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
} }
catch (Exception exception) catch (Exception exception)
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets the authentication result. /// Gets the authentication result.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientConnectResult ConnectResult { get; } public MqttClientConnectResult ConnectResult { get; }
} }
@@ -2,20 +2,35 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectOptions public sealed class MqttClientDisconnectOptions
{ {
/// <summary> /// <summary>
/// Gets or sets the reason code. /// Gets or sets the reason code.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection; public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
/// <summary> /// <summary>
/// Gets or sets the reason string. /// Gets or sets the reason string.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; set; } public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the session expiry interval.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
} }
} }
@@ -2,32 +2,62 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectOptionsBuilder public sealed class MqttClientDisconnectOptionsBuilder
{ {
MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection; MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
string _reasonString; string _reasonString;
uint _sessionExpiryInterval;
public MqttClientDisconnectOptionsBuilder WithReasonString(string value) List<MqttUserProperty> _userProperties;
{
_reasonString = value;
return this;
}
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value)
{
_reason = value;
return this;
}
public MqttClientDisconnectOptions Build() public MqttClientDisconnectOptions Build()
{ {
return new MqttClientDisconnectOptions return new MqttClientDisconnectOptions
{ {
Reason = _reason, Reason = _reason,
ReasonString = _reasonString ReasonString = _reasonString,
UserProperties = _userProperties,
SessionExpiryInterval = _sessionExpiryInterval
}; };
} }
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
{
_reason = value;
return this;
}
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
{
_reasonString = value;
return this;
}
public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
{
_sessionExpiryInterval = value;
return this;
}
public MqttClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> userProperties)
{
_userProperties = userProperties;
return this;
}
public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{
if (_userProperties == null)
{
_userProperties = new List<MqttUserProperty>();
}
_userProperties.Add(new MqttUserProperty(name, value));
return this;
}
} }
} }
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Client
{
/// <summary>
/// This enum only contains values which are valid when a client sends the reason to the server.
/// </summary>
public enum MqttClientDisconnectOptionsReason
{
NormalDisconnection = 0,
DisconnectWithWillMessage = 4,
UnspecifiedError = 128,
MalformedPacket = 129,
ProtocolError = 130,
ImplementationSpecificError = 131,
TopicNameInvalid = 144,
ReceiveMaximumExceeded = 147,
TopicAliasInvalid = 148,
PacketTooLarge = 149,
MessageRateTooHigh = 150,
QuotaExceeded = 151,
AdministrativeAction = 152,
PayloadFormatInvalid = 153
}
}
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using MQTTnet.Formatter;
namespace MQTTnet.Client
{
public static class MqttClientDisconnectOptionsValidator
{
public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (protocolVersion == MqttProtocolVersion.V500)
{
// Everything is supported.
return;
}
if (options.ReasonString?.Any() == true)
{
Throw(nameof(options.ReasonString));
}
if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection)
{
Throw(nameof(options.Reason));
}
}
static void Throw(string featureName)
{
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
}
}
}
@@ -3,27 +3,47 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectedEventArgs : EventArgs public sealed class MqttClientDisconnectedEventArgs : EventArgs
{ {
public bool ClientWasConnected { get; internal set; } public MqttClientDisconnectedEventArgs(
bool clientWasConnected,
MqttClientConnectResult connectResult,
MqttClientDisconnectReason reason,
string reasonString,
List<MqttUserProperty> userProperties,
Exception exception)
{
ClientWasConnected = clientWasConnected;
ConnectResult = connectResult;
Exception = exception;
Reason = reason;
ReasonString = reasonString;
UserProperties = userProperties;
}
public Exception Exception { get; internal set; } public bool ClientWasConnected { get; }
/// <summary> /// <summary>
/// Gets the authentication result. /// Gets the authentication result.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientConnectResult ConnectResult { get; internal set; } public MqttClientConnectResult ConnectResult { get; }
public Exception Exception { get; }
/// <summary> /// <summary>
/// Gets or sets the reason. /// Gets or sets the reason.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientDisconnectReason Reason { get; internal set; } public MqttClientDisconnectReason Reason { get; }
public string ReasonString { get; internal set; } public string ReasonString { get; }
public List<MqttUserProperty> UserProperties { get; }
} }
} }
+1 -1
View File
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync; event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPackage; event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
bool IsConnected { get; } bool IsConnected { get; }
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Client.Internal
{
public sealed class MqttClientEvents
{
public AsyncEvent<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceivedEvent { get; } = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
public AsyncEvent<MqttClientConnectingEventArgs> ConnectingEvent { get; } = new AsyncEvent<MqttClientConnectingEventArgs>();
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
public AsyncEvent<InspectMqttPacketEventArgs> InspectPacketEvent { get; } = new AsyncEvent<InspectMqttPacketEventArgs>();
}
}
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Client.Internal
{
public static class MqttClientResultFactory
{
public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory();
public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory();
public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory();
public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory();
}
}
File diff suppressed because it is too large Load Diff
+39 -8
View File
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
{ {
public static class MqttClientExtensions public static class MqttClientExtensions
{ {
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null) public static Task DisconnectAsync(
this IMqttClient client,
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
string reasonString = null,
uint sessionExpiryInterval = 0,
List<MqttUserProperty> userProperties = null,
CancellationToken cancellationToken = default)
{ {
if (client == null) if (client == null)
{ {
throw new ArgumentNullException(nameof(client)); throw new ArgumentNullException(nameof(client));
} }
return client.DisconnectAsync( var disconnectOptions = new MqttClientDisconnectOptions
new MqttClientDisconnectOptions {
{ Reason = reason,
Reason = reason, ReasonString = reasonString,
ReasonString = reasonString SessionExpiryInterval = sessionExpiryInterval,
}, UserProperties = userProperties
CancellationToken.None); };
return client.DisconnectAsync(disconnectOptions, cancellationToken);
} }
public static Task<MqttClientPublishResult> PublishBinaryAsync( public static Task<MqttClientPublishResult> PublishBinaryAsync(
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
} }
public static async Task<bool> TryDisconnectAsync(
this IMqttClient client,
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
string reasonString = null)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
try
{
await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false);
return true;
}
catch
{
// Ignore all errors.
}
return false;
}
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default) public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{ {
if (client == null) if (client == null)
@@ -0,0 +1,36 @@
// 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.Security.Cryptography.X509Certificates;
namespace MQTTnet.Client
{
public sealed class DefaultMqttCertificatesProvider : IMqttClientCertificatesProvider
{
readonly X509Certificate2Collection _certificates;
public DefaultMqttCertificatesProvider(X509Certificate2Collection certificates)
{
_certificates = certificates;
}
public DefaultMqttCertificatesProvider(IEnumerable<X509Certificate> certificates)
{
if (certificates != null)
{
_certificates = new X509Certificate2Collection();
foreach (var certificate in certificates)
{
_certificates.Add(certificate);
}
}
}
public X509CertificateCollection GetCertificates()
{
return _certificates;
}
}
}
@@ -0,0 +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.
namespace MQTTnet.Client
{
public interface IMqttClientCertificatesProvider
{
System.Security.Cryptography.X509Certificates.X509CertificateCollection GetCertificates();
}
}
@@ -14,8 +14,14 @@ namespace MQTTnet.Client
public X509Chain Chain { get; set; } public X509Chain Chain { get; set; }
public SslPolicyErrors SslPolicyErrors { get; set; }
public IMqttClientChannelOptions ClientOptions { get; set; } public IMqttClientChannelOptions ClientOptions { get; set; }
#if NET452 || NET461 || NET48
/// <summary>
/// Can be a host string name or an object derived from WebRequest.
/// </summary>
public object Sender { get; set; }
#endif
public SslPolicyErrors SslPolicyErrors { get; set; }
} }
} }
@@ -12,15 +12,23 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientOptions public sealed class MqttClientOptions
{ {
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _false_.
/// </summary>
public bool AllowPacketFragmentation { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the authentication data. /// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public byte[] AuthenticationData { get; set; } public byte[] AuthenticationData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the authentication method. /// Gets or sets the authentication method.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string AuthenticationMethod { get; set; } public string AuthenticationMethod { get; set; }
@@ -58,6 +66,10 @@ namespace MQTTnet.Client
/// </summary> /// </summary>
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15); public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
/// <summary>
/// Gets or sets the maximum packet size.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint MaximumPacketSize { get; set; } public uint MaximumPacketSize { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
@@ -65,27 +77,34 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the receive maximum. /// Gets or sets the receive maximum.
/// This gives the maximum length of the receive messages. /// This gives the maximum length of the receive messages.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public ushort ReceiveMaximum { get; set; } public ushort ReceiveMaximum { get; set; }
/// <summary> /// <summary>
/// Gets or sets the request problem information. /// Gets or sets the request problem information.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public bool RequestProblemInformation { get; set; } = true; public bool RequestProblemInformation { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the request response information. /// Gets or sets the request response information.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public bool RequestResponseInformation { get; set; } public bool RequestResponseInformation { get; set; }
/// <summary> /// <summary>
/// Gets or sets the session expiry interval. /// Gets or sets the session expiry interval.
/// The time after a session expires when it's not actively used. /// The time after a session expires when it's not actively used.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public uint SessionExpiryInterval { get; set; } public uint SessionExpiryInterval { get; set; }
/// <summary>
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
/// </summary>
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the timeout which will be applied at socket level and internal operations. /// Gets or 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. /// The default value is the same as for sockets in .NET in general.
@@ -95,6 +114,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the topic alias maximum. /// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias. /// This gives the maximum length of the topic alias.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public ushort TopicAliasMaximum { get; set; } public ushort TopicAliasMaximum { get; set; }
@@ -103,8 +123,10 @@ namespace MQTTnet.Client
/// client. /// client.
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated /// If successful, this means that loop detection will be more effective and that retained messages will be propagated
/// correctly. /// correctly.
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect /// <remarks>
/// properly. /// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not
/// connect properly.
/// </remarks>
/// </summary> /// </summary>
public bool TryPrivate { get; set; } = true; public bool TryPrivate { get; set; } = true;
@@ -115,28 +137,41 @@ namespace MQTTnet.Client
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber. /// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept. /// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; set; } public List<MqttUserProperty> UserProperties { get; set; }
/// <summary>
/// When this feature is enabled the client will check if used properties are supported in the selected protocol
/// version.
/// This feature can be validated if an application message is generated one time but sent via different protocol
/// versions.
/// Default values are applied if the validation is off and features are not supported.
/// </summary>
public bool ValidateFeatures { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the content type of the will message. /// Gets or sets the content type of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string WillContentType { get; set; } public string WillContentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the correlation data of the will message. /// Gets or sets the correlation data of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public byte[] WillCorrelationData { get; set; } public byte[] WillCorrelationData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the will delay interval. /// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent. /// This is the time between the client disconnect and the time the will message will be sent.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public uint WillDelayInterval { get; set; } public uint WillDelayInterval { get; set; }
/// <summary> /// <summary>
/// Gets or sets the message expiry interval of the will message. /// Gets or sets the message expiry interval of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public uint WillMessageExpiryInterval { get; set; } public uint WillMessageExpiryInterval { get; set; }
@@ -147,8 +182,9 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the payload format indicator of the will message. /// Gets or sets the payload format indicator of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
/// <summary> /// <summary>
/// Gets or sets the QoS level of the will message. /// Gets or sets the QoS level of the will message.
@@ -157,6 +193,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the response topic of the will message. /// Gets or sets the response topic of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string WillResponseTopic { get; set; } public string WillResponseTopic { get; set; }
@@ -172,6 +209,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the user properties of the will message. /// Gets or sets the user properties of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> WillUserProperties { get; set; } public List<MqttUserProperty> WillUserProperties { get; set; }

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