Compare commits

...
Author SHA1 Message Date
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
Christian f79649d71b 1612 mqttserver subscription to similar base and subtopic with wildcards fails (#1622) 2022-12-07 20:59:52 +01:00
dependabot[bot] c9c27b9f01 Bump Newtonsoft.Json from 13.0.1 to 13.0.2 in /Source/MQTTnet.TestApp (#1621)
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-07 20:57:29 +01:00
Christian 0bc47cf853 Improve unit tests 2022-12-07 20:50:34 +01:00
Christian 5649328bc5 Update release notes 2022-12-07 20:44:30 +01:00
Christian 726dd15c5a Cleanup projects 2022-12-07 20:44:23 +01:00
Christian d513c6f85c Refactor code 2022-12-07 19:43:10 +01:00
老九 c1c17ba162 MqttConnectionContext: Adapting mqtt over tcp (#1616)
Adapting mqtt over tcp.
2022-12-07 19:36:13 +01:00
soyoo f8aa686557 A typo in comment. (#1618) 2022-12-07 17:08:15 +01:00
Christian d7eb4bbe6e Allow interception of will messages (#1613) 2022-12-06 20:28:01 +01:00
Christian c17a350796 1595 object disposed exception in unit test handle parallel connection attempts (#1610) 2022-12-05 19:22:18 +01:00
Christian bb106f8c6e Refactor code 2022-12-05 19:21:08 +01:00
老九 09e47fb5eb Avoid a split MqttPacketBuffer transfer caused by triggering PipeWriter.WriteAsync twice. (#1609)
Avoid a split MqttPacketBuffer transfer caused by triggering PipeWriter.FlushAsync twice.
2022-12-05 19:19:31 +01:00
Christian 099209fff2 Update README.md 2022-12-04 14:10:54 +01:00
Christian d572490a67 Use the net452 supporting async task completion source (#1607) 2022-12-04 13:39:50 +01:00
Christian 180e7f79d4 Update ci.yml 2022-12-03 14:50:30 +01:00
Christian d9da7cc92e Refactor code 2022-12-03 14:09:27 +01:00
Christian c2ce2044cc Merge branch 'master' of https://github.com/dotnet/MQTTnet 2022-12-03 13:19:38 +01:00
Christian e400bfb79e Update ci.yml 2022-12-03 13:19:37 +01:00
老九andChristian 3d32faa8f9 Avoid copying buffer from ReadOnlySequence as ReceivedMqttPacket body (#1596)
* Avoid copying buffer from ReadOnlySequence as  ReceivedMqttPacket body

* Use ReadOnlySequence.FirstSpan if possible.

* add ReaderExtensionsBenchmark

* update benchmark

* fix fixedHeader incorrect read

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2022-12-03 13:18:30 +01:00
老九andChristian 862d76471b Implement buffer copy using different policies (#1599)
use Memory-Copy

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2022-12-03 13:17:02 +01:00
老九 27f2474876 fix MqttBufferReader #1597 (#1598) 2022-12-03 13:14:07 +01:00
Christian 01a5aeeb58 Refactor benchmarks (#1601) 2022-11-30 18:09:34 +01:00
老九 47df131e7f Avoid to check the type of propertyWriter parameter (#1600)
Refactor code of MqttBufferWriter
2022-11-30 17:56:05 +01:00
Christian 56750adfe4 1592 bug of mqttbufferreader (#1593) 2022-11-29 20:23:20 +01:00
Christian 11e64ef018 Update ReleaseNotes.md 2022-11-28 21:10:46 +01:00
Christian 9b20e0a430 Refactor code 2022-11-28 21:10:42 +01:00
老九 41df2ec5da Optimize SendPacketAsync method for MQTTnet.AspNetCore (#1582)
* Avoid to call MqttPacketBuffer.Join() when SendPacketAsync

* Safely cleanup the PacketFormatterAdapter

* Keep buffer.Join when mqtt over webSocket

* null checking for IHttpContextFeature.HttpContext
2022-11-28 21:02:13 +01:00
Christian e45b207375 Update ReleaseNotes.md 2022-11-26 14:53:03 +01:00
Christian c37866e534 Refactor code and update release notes 2022-11-26 14:51:36 +01:00
老九 bda4ab7025 Set string.Empty as default value for SenderClientId (#1583) 2022-11-26 14:50:08 +01:00
Christian 32cc1ff7b1 Refactor code and update release notes 2022-11-26 14:49:28 +01:00
Dalibor Kundrat b5952a590a Extend server client ResetStatistics() to reset all client statistics not only channel... (#1587)
Extend reset client statistic to reset all stats
2022-11-26 14:47:17 +01:00
Christian 581a024ceb Improve allocations for AsyncSignal. 2022-11-26 14:45:34 +01:00
Mario ZelgerandMario Zelger c45cb8b04e Dispose the AsyncSignalWaiter before setting it to null (#1586)
dispose the AsyncSignalWaiter before setting it to null to prevent memory leak

Co-authored-by: Mario Zelger <mario.zelger@confer.ch>
2022-11-26 14:19:51 +01:00
Christian a5e1a4d6dd Update version 2022-11-26 14:16:37 +01:00
231 changed files with 7832 additions and 3302 deletions
+11 -1
View File
@@ -1 +1,11 @@
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar).
* [Client] Added hot reload of client certificates (#1781).
* [Client] Added several new option builders and aligned usage (#1781, BREAKING CHANGE!).
* [Client] Added support for _RemoteCertificateValidationCallback_ for .NET 4.5.2, 4.6.1 and 4.8 (#1806, thanks to @troky).
* [Client] Fixed wrong logging of obsolete feature when connection was not successful (#1801, thanks to @ramonsmits).
* [Client] Fixed _NullReferenceException_ when performing several actions when not connected (#1800, thanks to @ramonsmits).
* [RpcClient] Added support for passing custom parameters to topic generation context (#1798, thanks to @Temppus).
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
* [Server] Expired sessions will no longer be used when a client connects (#1756).
* [Server] Fixed an issue in connection handling for ASP.NET connections (#1819, thanks to @CZEMacLeod).
+3 -6
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.1.3.${{github.run_number}}"
VERSION: "4.3.0.${{github.run_number}}"
jobs:
build:
@@ -22,6 +22,7 @@ jobs:
dotnet-version: |
3.1.x
6.0.x
7.0.x
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
@@ -60,8 +61,4 @@ jobs:
- name: Publish MyGet nugets
if: ${{ github.event_name == 'push' }}
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
# - name: Publish nugets
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
+7 -1
View File
@@ -240,8 +240,14 @@ 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_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@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/=PUBACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREC/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREL/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=SUBACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+17 -1
View File
@@ -4,7 +4,7 @@
</p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg)
[![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -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
* 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
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+69 -23
View File
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
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()
{
/*
@@ -113,15 +135,15 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
.WithTls(
.WithTlsOptions(
o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true;
o.WithCertificateValidationHandler(_ => true);
// The default value is determined by the OS. Set manually to force version.
o.SslProtocol = SslProtocols.Tls12;
o.WithSslProtocols(SslProtocols.Tls12);
})
.Build();
@@ -138,7 +160,7 @@ public static class Client_Connection_Samples
{
/*
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
* encryption algorithms and supports more platforms.
*
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
@@ -148,7 +170,7 @@ public static class Client_Connection_Samples
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);
@@ -170,7 +192,7 @@ public static class Client_Connection_Samples
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);
@@ -193,13 +215,11 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithTls(
o =>
{
.WithTlsOptions(
o => o.WithCertificateValidationHandler(
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true;
})
_ => true))
.Build();
// In MQTTv5 the response contains much more information.
@@ -214,6 +234,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()
{
/*
@@ -232,7 +277,7 @@ public static class Client_Connection_Samples
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
}
}
@@ -269,18 +314,19 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTls(
.WithTlsOptions(
o =>
{
o.CertificateValidationHandler = eventArgs =>
{
eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole();
return true;
};
o.WithCertificateValidationHandler(
eventArgs =>
{
eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole();
return true;
});
})
.Build();
@@ -297,7 +343,7 @@ public static class Client_Connection_Samples
/*
* 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();
@@ -5,6 +5,7 @@
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
using MQTTnet.Client;
using MQTTnet.Packets;
@@ -176,4 +177,60 @@ public static class Client_Subscribe_Samples
response.DumpToConsole();
}
}
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
*/
mqttClient.ApplicationMessageReceivedAsync += async ea =>
{
ea.AutoAcknowledge = false;
async Task ProcessAsync()
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
await ea.AcknowledgeAsync(shutdownToken);
// WARNING: If process failures are not transient the message will be retried on every restart of the client
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
// the broker know processing failed
//
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
}
_ = Task.Run(ProcessAsync, shutdownToken);
};
}
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing, with:
* - a maximum concurrency limit based on Environment.ProcessorCount
*/
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
mqttClient.ApplicationMessageReceivedAsync += async ea =>
{
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
async Task ProcessAsync()
{
try
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
}
finally
{
concurrent.Release();
}
}
_ = Task.Run(ProcessAsync, shutdownToken);
};
}
}
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
.WithTcpServer("broker.hivemq.com")
.Build();
mqttClient.InspectPackage += OnInspectPackage;
mqttClient.InspectPacketAsync += OnInspectPacket;
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)
{
+1 -3
View File
@@ -9,12 +9,10 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
+18 -1
View File
@@ -4,7 +4,13 @@
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();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
index++;
}
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0");
var selectedSampleClass = sampleClasses[selectedIndex];
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
index++;
}
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor();
input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0");
var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Executing sample...");
Console.ResetColor();
Console.WriteLine();
try
@@ -17,7 +17,7 @@ using MQTTnet.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()
{
@@ -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
using System.Text.Json;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.Server;
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
{
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.");
}
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
// 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);
Console.WriteLine("Retained messages saved.");
}
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
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;
}
}
}
}
@@ -12,8 +12,6 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
@@ -18,7 +18,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Helpers;
namespace MQTTnet.AspNetCore.Tests
{
@@ -35,7 +35,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe;
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();
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 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);
@@ -8,17 +8,15 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\"/>
<Folder Include="wwwroot\" />
</ItemGroup>
</Project>
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_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()
#else
public Task DisposeAsync()
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
#if NETCOREAPP3_1_OR_GREATER
return base.DisposeAsync();
}
@@ -3,7 +3,7 @@
// 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.Routing;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<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>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
@@ -32,7 +32,8 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
@@ -50,11 +51,11 @@
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</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" />
</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" />
</ItemGroup>
@@ -2,38 +2,62 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO.Pipelines;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.IO.Pipelines;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Packets;
namespace MQTTnet.AspNetCore
{
public sealed class MqttConnectionContext : IMqttChannelAdapter
{
readonly ConnectionContext _connection;
readonly AsyncLock _writerLock = new AsyncLock();
PipeReader _input;
PipeWriter _output;
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
if (Connection.Transport != null)
if (!(_connection is TcpConnection tcp) || tcp.IsConnected)
{
_input = Connection.Transport.Input;
_output = Connection.Transport.Output;
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
}
public long BytesReceived { get; private set; }
public long BytesSent { get; private set; }
public X509Certificate2 ClientCertificate
{
get
{
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{
return tlsFeature.ClientCertificate;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
return httpFeature?.HttpContext?.Connection.ClientCertificate;
}
}
@@ -41,47 +65,59 @@ namespace MQTTnet.AspNetCore
{
get
{
#if NETCOREAPP3_1
if (Connection?.RemoteEndPoint != null)
// mqtt over tcp
#if NETCOREAPP3_1_OR_GREATER
if (_connection.RemoteEndPoint != null)
{
return Connection.RemoteEndPoint.ToString();
return _connection.RemoteEndPoint.ToString();
}
#endif
var connection = Http?.HttpContext?.Connection;
if (connection == null)
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.RemoteIpAddress != null)
{
return Connection.ConnectionId;
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
}
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
return null;
}
}
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
public ConnectionContext Connection { get; }
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public long BytesSent { get; set; }
public long BytesReceived { get; set; }
public bool IsReadingPacket { get; private set; }
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
public bool IsSecureConnection
{
get
{
// mqtt over tcp
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
if (tlsFeature != null)
{
return true;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
if (httpFeature?.HttpContext != null)
{
return httpFeature.HttpContext.Request.IsHttps;
}
return false;
}
}
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (Connection is TcpConnection tcp && !tcp.IsConnected)
if (_connection is TcpConnection tcp && !tcp.IsConnected)
{
await tcp.StartAsync().ConfigureAwait(false);
}
_input = Connection.Transport.Input;
_output = Connection.Transport.Output;
_input = _connection.Transport.Input;
_output = _connection.Transport.Output;
}
public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -92,16 +128,19 @@ namespace MQTTnet.AspNetCore
return Task.CompletedTask;
}
public void Dispose()
{
_writerLock.Dispose();
}
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
{
var input = Connection.Transport.Input;
try
{
while (!cancellationToken.IsCancellationRequested)
{
ReadResult readResult;
var readTask = input.ReadAsync(cancellationToken);
var readTask = _input.ReadAsync(cancellationToken);
if (readTask.IsCompleted)
{
readResult = readTask.Result;
@@ -141,15 +180,15 @@ namespace MQTTnet.AspNetCore
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
_input.AdvanceTo(consumed, observed);
}
}
}
catch (Exception e)
catch (Exception exception)
{
// completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(e);
_output?.Complete(e);
_input?.Complete(exception);
_output?.Complete(exception);
throw;
}
finally
@@ -169,25 +208,44 @@ namespace MQTTnet.AspNetCore
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
var formatter = PacketFormatterAdapter;
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory();
var output = _output;
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
if (result.IsCompleted)
try
{
BytesSent += msg.Length;
}
var buffer = PacketFormatterAdapter.Encode(packet);
PacketFormatterAdapter.Cleanup();
if (buffer.Payload.Count == 0)
{
// zero copy
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
}
else
{
WritePacketBuffer(_output, buffer);
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
}
BytesSent += buffer.Length;
}
finally
{
PacketFormatterAdapter.Cleanup();
}
}
}
public void Dispose()
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);
}
}
}
}
@@ -2,38 +2,34 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
using System;
using System.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore
{
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{
IMqttNetLogger _logger = new MqttNetNullLogger();
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
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)
{
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
if (webSocket == null)
{
throw new ArgumentNullException(nameof(webSocket));
}
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
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);
}
@@ -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;
}
}
}
+50 -23
View File
@@ -2,21 +2,23 @@
// 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.Buffers;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore
{
public static class ReaderExtensions
{
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
return false;
}
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
{
return false;
}
var fixedHeader = copy.First.Span[0];
copy = copy.Slice(headerLength);
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = bodySlice.GetMemory().ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
var bodySegment = GetArraySegment(ref bodySlice);
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
return true;
}
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
{
return input.First;
return segment;
}
// Should be rare
return input.ToArray();
var array = input.ToArray();
return new ArraySegment<byte>(array);
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
if (valueSequence.IsSingleSegment)
{
var valueSpan =
#if NET5_0_OR_GREATER
valueSequence.FirstSpan;
#else
valueSequence.First.Span;
#endif
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
else
{
Span<byte> valueSpan = stackalloc byte[8];
valueSequence.CopyTo(valueSpan);
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
}
}
static bool TryReadBodyLength(ReadOnlySpan<byte> span, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
var index = 1;
headerLength = 0;
bodyLength = 0;
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
do
{
if (index == temp.Length)
if (index == span.Length)
{
return false;
}
encodedByte = temp[index];
encodedByte = span[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
ThrowProtocolViolationException(span, index);
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index;
bodyLength = value;
return true;
}
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
}
}
}
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Benchmarks
{
public abstract class BaseBenchmark
@@ -2,54 +2,25 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using System;
using System.IO;
using System.Threading;
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark
public sealed class ChannelAdapterBenchmark : BaseBenchmark
{
MqttChannelAdapter _channelAdapter;
int _iterations;
MemoryStream _stream;
MqttPublishPacket _packet;
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
MemoryStream _stream;
[Benchmark]
public void Receive_10000_Messages()
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
_stream.Position = 0;
}
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
}
static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
return buffer.ToArray();
}
}
}
}
@@ -1,22 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Validators;
namespace MQTTnet.Benchmarks.Configurations
{
/// <summary>
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
/// https://benchmarkdotnet.org/Configs/Configs.htm
/// </summary>
public class AllowNonOptimized : BaseConfig
{
public AllowNonOptimized()
{
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
AddJob(Job.InProcess);
}
}
}
@@ -1,19 +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 BenchmarkDotNet.Configs;
using System.Linq;
namespace MQTTnet.Benchmarks.Configurations
{
public class BaseConfig : ManualConfig
{
public BaseConfig()
{
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
}
}
}
@@ -1,19 +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 BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.CsProj;
namespace MQTTnet.Benchmarks.Configurations
{
public class RuntimeCompareConfig : BaseConfig
{
public RuntimeCompareConfig()
{
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark
public class LoggerBenchmark : BaseBenchmark
{
MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger;
@@ -1,14 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType>
<DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -24,11 +26,12 @@
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
</ItemGroup>
@@ -0,0 +1,40 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MemoryCopyBenchmark
{
const int max_length = 1024 * 8;
private byte[] source;
private byte[] target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; }
[GlobalSetup]
public void Setup()
{
source = new byte[max_length];
target = new byte[max_length];
}
[Benchmark(Baseline = true)]
public void Array_Copy()
{
Array.Copy(source, 0, target, 0, Length);
}
[Benchmark]
public void Memory_Copy()
{
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
}
}
}
@@ -1,5 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System;
@@ -8,14 +11,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
/// <summary>
/// Create a number of topics, publish, subscribe, and wait for response
/// </summary>
[MemoryDiagnoser]
public class MessageDeliveryBenchmark
public class MessageDeliveryBenchmark : BaseBenchmark
{
List<MqttApplicationMessage> _topicPublishMessages;
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MessageProcessingBenchmark
public class MessageProcessingBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
{
IWebHost _host;
IMqttClient _mqttClient;
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttBufferReaderBenchmark
{
byte[] _buffer;
int _bufferLength;
[GlobalSetup]
public void GlobalSetup()
{
var writer = new MqttBufferWriter(1024, 1024);
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
_buffer = writer.GetBuffer();
_bufferLength = writer.Length;
}
[Benchmark]
public void Use_Span()
{
var span = _buffer.AsSpan(0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark]
public void Use_Encoding()
{
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
}
}
}
@@ -1,7 +1,10 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore;
using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups;
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
{
readonly byte[] _demoPayload = new byte[1024];
@@ -27,17 +30,17 @@ namespace MQTTnet.Benchmarks
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
@@ -80,11 +83,11 @@ namespace MQTTnet.Benchmarks
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589);
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.Reset(0);
}
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public sealed class MqttTcpChannelBenchmark
public class MqttTcpChannelBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttChannel _serverChannel;
+122 -67
View File
@@ -3,85 +3,140 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using MQTTnet.Benchmarks.Configurations;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
Console.WriteLine("--------------------------------------------------------");
Console.WriteLine("1 = MessageProcessingBenchmark");
Console.WriteLine("2 = SerializerBenchmark");
Console.WriteLine("3 = LoggerBenchmark");
Console.WriteLine("4 = TopicFilterComparerBenchmark");
Console.WriteLine("5 = ChannelAdapterBenchmark");
Console.WriteLine("6 = MqttTcpChannelBenchmark");
Console.WriteLine("7 = TcpPipesBenchmark");
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
Console.WriteLine("9 = ServerProcessingBenchmark");
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
Console.WriteLine("b = RoundtripBenchmark");
Console.WriteLine("c = SubscribeBenchmark");
Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark");
Console.WriteLine("f = AsyncLockBenchmark");
static int _selectedBenchmarkIndex;
static List<Type> _benchmarks;
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
public static void Main(string[] arguments)
{
_benchmarks = CollectBenchmarks();
HandleArguments(arguments);
while (true)
{
case '1':
BenchmarkRunner.Run<MessageProcessingBenchmark>();
break;
case '2':
BenchmarkRunner.Run<SerializerBenchmark>();
break;
case '3':
BenchmarkRunner.Run<LoggerBenchmark>();
break;
case '4':
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
break;
case '5':
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
break;
case '6':
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
break;
case '7':
BenchmarkRunner.Run<TcpPipesBenchmark>();
break;
case '8':
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
break;
case '9':
BenchmarkRunner.Run<ServerProcessingBenchmark>();
break;
case 'a':
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
break;
case 'b':
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
break;
case 'c':
BenchmarkRunner.Run<SubscribeBenchmark>();
break;
case 'd':
BenchmarkRunner.Run<UnsubscribeBenchmark>();
break;
case 'e':
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
break;
case 'f':
BenchmarkRunner.Run<AsyncLockBenchmark>();
RenderMenu();
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{
_selectedBenchmarkIndex++;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = 0;
}
}
else if (key.Key == ConsoleKey.UpArrow)
{
_selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
else if (key.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
else if (key.Key == ConsoleKey.Enter)
{
break;
}
}
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static List<Type> CollectBenchmarks()
{
var benchmarks = new List<Type>();
var types = typeof(Program).Assembly.GetExportedTypes();
foreach (var type in types)
{
if (typeof(BaseBenchmark).IsAssignableFrom(type))
{
if (type != typeof(BaseBenchmark))
{
benchmarks.Add(type);
}
}
}
return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
{
return;
}
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
{
_selectedBenchmarkIndex = benchmarkIndex;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
static void RenderMenu()
{
Console.Clear();
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection");
Console.WriteLine("Press Enter for benchmark execution");
Console.WriteLine("Press Esc for exit");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
for (var index = 0; index < _benchmarks.Count; index++)
{
var benchmark = _benchmarks[index];
if (_selectedBenchmarkIndex == index)
{
Console.Write("-> ");
}
else
{
Console.Write(" ");
}
Console.WriteLine(benchmark.Name);
}
Console.WriteLine();
}
}
}
}
@@ -0,0 +1,240 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
MqttPacketFormatterAdapter mqttPacketFormatter;
MemoryStream stream;
[GlobalSetup]
public void GlobalSetup()
{
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder()
.WithTopic("topic")
.WithPayload(new byte[10 * 1024])
.Build();
var packet = MqttPacketFactories.Publish.Create(mqttMessage);
var buffer = mqttPacketFormatter.Encode(packet);
stream = new MemoryStream();
stream.Write(buffer.Packet);
stream.Write(buffer.Payload);
mqttPacketFormatter.Cleanup();
}
[Benchmark(Baseline = true)]
public async Task Before()
{
stream.Position = 0;
var input = PipeReader.Create(stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
[Benchmark]
public async Task After()
{
stream.Position = 0;
var input = PipeReader.Create(stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
public static class ReaderExtensions_Before
{
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
packet = null;
consumed = input.Start;
observed = input.End;
bytesRead = 0;
var copy = input;
if (copy.Length < 2)
{
return false;
}
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{
return false;
}
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = GetMemory(bodySlice).ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
}
packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
bytesRead = headerLength + bodyLength;
return true;
}
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
{
return input.First;
}
// Should be rare
return input.ToArray();
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
var value = 0;
byte encodedByte;
var index = 1;
headerLength = 0;
bodyLength = 0;
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
do
{
if (index == temp.Length)
{
return false;
}
encodedByte = temp[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index;
bodyLength = value;
return true;
}
}
}
}
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark
public class RoundtripProcessingBenchmark : BaseBenchmark
{
[GlobalSetup]
public void GlobalSetup()
@@ -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()
{
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++)
{
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups;
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark
public class ServerProcessingBenchmark : BaseBenchmark
{
[GlobalSetup]
public void GlobalSetup()
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
@@ -8,7 +12,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public class SubscribeBenchmark
public class SubscribeBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class TcpPipesBenchmark
public class TcpPipesBenchmark : BaseBenchmark
{
IDuplexPipe _client;
IDuplexPipe _server;
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark
public class TopicFilterComparerBenchmark : BaseBenchmark
{
static readonly char[] TopicLevelSeparator = { '/' };
+18 -15
View File
@@ -1,31 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MQTTnet.Benchmarks
{
public class TopicGenerator
{
public static void Generate(
int numPublishers, int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
)
int numPublishers,
int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
{
topicsByPublisher = new Dictionary<string, List<string>>();
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
{
topicsPerLevel = 1;
}
int numLevel1Topics = topicsPerLevel;
int numLevel2Topics = topicsPerLevel;
var numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
for (var p = 0; p < numPublishers; ++p)
{
int publisherTopicCount = 0;
var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{
if (publisherTopicCount >= numTopicsPerPublisher)
{
break;
}
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if ((l1 == 0) && (l3 == 0))
if (l1 == 0 && l3 == 0)
{
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
}
}
}
}
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BenchmarkDotNet.Attributes;
using MQTTnet.Client;
using MQTTnet.Server;
@@ -7,7 +11,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
public class UnsubscribeBenchmark
public class UnsubscribeBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
-54
View File
@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
<package id="System.Console" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
</packages>
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</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;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
@@ -33,7 +33,7 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
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.");
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");
List<MqttTopicFilter> topicFilters = null;
try
{
if (_reconnectSubscriptions.Any())
{
var topicFilters = new List<MqttTopicFilter>();
topicFilters = new List<MqttTopicFilter>();
foreach (var sub in _reconnectSubscriptions)
{
@@ -479,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
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);
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
}
if (addedSubscriptions != null && addedSubscriptions.Any())
@@ -607,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
catch (Exception exception)
{
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
}
}
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
/// <summary>
/// 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>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
}
@@ -2,17 +2,33 @@
// 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.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient
{
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));
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 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)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
}
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -7,8 +8,8 @@ namespace MQTTnet.Extensions.Rpc
{
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">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</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;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
@@ -33,7 +33,8 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
{
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
{
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
return factory.CreateMqttRpcClient(
mqttClient,
new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
}
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
+48 -50
View File
@@ -2,23 +2,27 @@
// 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.Client;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClient : IMqttRpcClient
{
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -27,13 +31,25 @@ namespace MQTTnet.Extensions.Rpc
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
{
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)
{
@@ -47,17 +63,15 @@ 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) throw new ArgumentNullException(nameof(methodName));
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
if (methodName == null)
{
MethodName = methodName,
QualityOfServiceLevel = qualityOfServiceLevel,
MqttClient = _mqttClient,
Options = _options
});
throw new ArgumentNullException(nameof(methodName));
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
@@ -72,34 +86,34 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessage = new MqttApplicationMessageBuilder()
.WithTopic(requestTopic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithResponseTopic(responseTopic)
.Build();
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
#if NET452
var awaitable = new TaskCompletionSource<byte[]>();
#else
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
#endif
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(responseTopic, qualityOfServiceLevel)
.Build();
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
using (cancellationToken.Register(
() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
@@ -107,8 +121,7 @@ namespace MQTTnet.Extensions.Rpc
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -119,28 +132,13 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance;
}
#if NET452
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
#else
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
#endif
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
}
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
}
}
}
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -11,13 +12,13 @@ namespace MQTTnet.Extensions.Rpc
{
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));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
}
}
}
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
{
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
public MqttRpcClientOptions Build()
{
return new MqttRpcClientOptions
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
TopicGenerationStrategy = _topicGenerationStrategy
};
}
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
}
}
}
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{
throw new ArgumentException("The method name cannot contain /, + or #.");
@@ -2,6 +2,8 @@
// 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 MQTTnet.Client;
using MQTTnet.Protocol;
@@ -9,20 +11,33 @@ namespace MQTTnet.Extensions.Rpc
{
public sealed class TopicGenerationContext
{
public string MethodName { get; set; }
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
/// that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public IMqttClient MqttClient { get; set; }
public MqttRpcClientOptions Options { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
}
}
}
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</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;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -33,7 +33,8 @@
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591</NoWarn>
<NoWarn>1591;NETSDK1138</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
{
if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
if (mqttFactory == null)
{
throw new ArgumentNullException(nameof(mqttFactory));
}
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
}
}
}
}
@@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Security.Authentication;
@@ -17,14 +18,14 @@ using MQTTnet.Exceptions;
using MQTTnet.Internal;
using SuperSocket.ClientEngine;
using WebSocket4Net;
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
namespace MQTTnet.Extensions.WebSocket4Net
{
public sealed class WebSocket4NetMqttChannel : IMqttChannel
{
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
readonly MqttClientOptions _clientOptions;
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
readonly MqttClientWebSocketOptions _webSocketOptions;
WebSocket _webSocket;
@@ -35,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
}
public X509Certificate2 ClientCertificate { get; }
public string Endpoint => _webSocketOptions.Uri;
public bool IsSecureConnection { get; private set; }
public X509Certificate2 ClientCertificate { get; }
public async Task ConnectAsync(CancellationToken cancellationToken)
public Task ConnectAsync(CancellationToken cancellationToken)
{
var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
@@ -55,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
uri = "wss://" + uri;
}
}
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
#if NET48 || NETCOREAPP3_0_OR_GREATER
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
#else
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
@@ -89,33 +92,20 @@ namespace MQTTnet.Extensions.WebSocket4Net
var webSocketVersion = WebSocketVersion.None;
var receiveBufferSize = 0;
var certificates = new X509CertificateCollection();
if (_webSocketOptions?.TlsOptions?.Certificates != null)
{
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
{
#if WINDOWS_UWP
certificates.Add(new X509Certificate(certificate));
#else
certificates.Add(certificate);
#endif
}
}
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
{
NoDelay = true,
Security =
{
AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
Certificates = certificates
}
};
await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
return ConnectInternalAsync(cancellationToken);
}
public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -124,10 +114,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
_webSocket.Close();
}
return CompletedTask.Instance;
}
public void Dispose()
{
if (_webSocket == null)
{
return;
}
_webSocket.DataReceived -= OnDataReceived;
_webSocket.Error -= OnError;
_webSocket.Dispose();
_webSocket = null;
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var readBytes = 0;
@@ -161,75 +164,55 @@ namespace MQTTnet.Extensions.WebSocket4Net
return CompletedTask.Instance;
}
public void Dispose()
{
if (_webSocket == null)
{
return;
}
_webSocket.DataReceived -= OnDataReceived;
_webSocket.Error -= OnError;
_webSocket.Dispose();
_webSocket = null;
}
static void OnError(object sender, ErrorEventArgs e)
{
System.Diagnostics.Debug.Write(e.Exception.ToString());
}
void OnDataReceived(object sender, DataReceivedEventArgs e)
{
foreach (var @byte in e.Data)
{
_receiveBuffer.Add(@byte);
}
}
async Task ConnectInternalAsync(CancellationToken cancellationToken)
{
_webSocket.Error += OnError;
_webSocket.DataReceived += OnDataReceived;
var taskCompletionSource = new TaskCompletionSource<Exception>();
var promise = new AsyncTaskCompletionSource<bool>();
void ErrorHandler(object sender, ErrorEventArgs e)
{
taskCompletionSource.TrySetResult(e.Exception);
promise.TrySetException(e.Exception);
}
void SuccessHandler(object sender, EventArgs e)
void OpenedHandler(object sender, EventArgs e)
{
taskCompletionSource.TrySetResult(null);
promise.TrySetResult(true);
}
void ClosedHandler(object sender, EventArgs e)
{
promise.TrySetException(new MqttCommunicationException("Connection closed."));
}
try
{
_webSocket.Opened += SuccessHandler;
_webSocket.Opened += OpenedHandler;
_webSocket.Closed += ClosedHandler;
_webSocket.Error += ErrorHandler;
#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
_webSocket.Open();
#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
{
using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
{
try
{
await taskCompletionSource.Task.ConfigureAwait(false);
// OpenAsync does basically the same but without support for a cancellation token.
// So we do it on our own!
_webSocket.Open();
await promise.Task.ConfigureAwait(false);
}
catch (Exception exception)
{
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
if (exception is AuthenticationException authenticationException)
{
throw new MqttCommunicationException(authenticationException.InnerException);
@@ -251,9 +234,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
}
finally
{
_webSocket.Opened -= SuccessHandler;
_webSocket.Opened -= OpenedHandler;
_webSocket.Closed -= ClosedHandler;
_webSocket.Error -= ErrorHandler;
}
}
void OnDataReceived(object sender, DataReceivedEventArgs e)
{
foreach (var @byte in e.Data)
{
_receiveBuffer.Add(@byte);
}
}
static void OnError(object sender, ErrorEventArgs e)
{
Debug.Write(e.Exception.ToString());
}
}
}
}
@@ -2,12 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using System;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
namespace MQTTnet.Extensions.WebSocket4Net
{
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
switch (options.ChannelOptions)
{
@@ -23,25 +26,29 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
return new MqttChannelAdapter(
new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
packetInspector,
logger);
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
logger)
{
PacketInspector = packetInspector
};
}
case MqttClientWebSocketOptions webSocketOptions:
{
return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
packetInspector,
logger);
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
logger)
{
PacketInspector = packetInspector
};
}
default:
{
throw new NotSupportedException();
}
{
throw new NotSupportedException();
}
}
}
}
}
}
+10 -1
View File
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
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($"+ 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($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine();
@@ -1,20 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<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>
<IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>
+4 -4
View File
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
return new MqttApplicationMessage
{
Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes(Payload),
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
};
}
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage
{
Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"),
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
};
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage
{
Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"),
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
};
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage
{
Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"),
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
};
+111 -78
View File
@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Client;
using System;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
@@ -17,13 +18,9 @@ namespace MQTTnet.TestApp
{
public static async Task RunAsync()
{
// MqttNetConsoleLogger.ForwardToConsole();
// For most of these connections to work, set output target to Net5.0.
#if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
var unsafeTls13 = new MqttClientTlsOptions
{
UseTls = true,
SslProtocol = SslProtocols.Tls13,
@@ -31,9 +28,8 @@ namespace MQTTnet.TestApp
CertificateValidationHandler = w => true
};
#endif
// Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
var unsafeTls12 = new MqttClientTlsOptions
{
UseTls = true,
SslProtocol = SslProtocols.Tls12,
@@ -42,85 +38,116 @@ namespace MQTTnet.TestApp
};
// mqtt.eclipseprojects.io
await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"mqtt.eclipseprojects.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
true);
#endif
// test.mosquitto.org
await ExecuteTestAsync("test.mosquitto.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
.WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org TCP - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
#endif
await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12 - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTlsOptions(unsafeTls12)
.Build());
await ExecuteTestAsync("test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"test.mosquitto.org WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestAsync(
"test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
// await ExecuteTestAsync(
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
// true);
// broker.emqx.io
await ExecuteTestAsync("broker.emqx.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.emqx.io TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
#endif
await ExecuteTestAsync("broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestAsync(
"broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
true);
// broker.hivemq.com
await ExecuteTestAsync("broker.hivemq.com TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync("broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
// mqtt.swifitch.cz: Does not seem to operate any more
@@ -130,36 +157,43 @@ namespace MQTTnet.TestApp
Console.ReadLine();
}
static async Task ExecuteTestAsync(string name, MqttClientOptions options)
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
{
try
{
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
var factory = new MqttFactory();
//factory.UseWebSocket4Net();
var client = factory.CreateMqttClient();
var topic = Guid.NewGuid().ToString();
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceivedAsync += e =>
if (useWebSocket4Net)
{
receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance;
};
await client.ConnectAsync(options);
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
{
throw new Exception("Message invalid.");
factory.UseWebSocket4Net();
}
await client.UnsubscribeAsync(topic);
await client.DisconnectAsync();
using (var client = factory.CreateMqttClient())
{
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return CompletedTask.Instance;
};
await client.ConnectAsync(options).ConfigureAwait(false);
var topic = Guid.NewGuid().ToString();
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
{
throw new Exception("Message invalid.");
}
await client.UnsubscribeAsync(topic).ConfigureAwait(false);
await client.DisconnectAsync().ConfigureAwait(false);
}
Write("[OK]\n", ConsoleColor.Green);
}
@@ -174,6 +208,5 @@ namespace MQTTnet.TestApp
Console.ForegroundColor = color;
Console.Write(message);
}
}
}
}
+11 -4
View File
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
{
// Replace the payload with the timestamp. But also extending a JSON
// 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")
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
mqttServer.InterceptingPublishAsync += e =>
{
MqttNetConsoleLogger.PrintToConsole(
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
ConsoleColor.Magenta);
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);
}
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
return CompletedTask.Instance;
};
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{
[TestClass]
public sealed class LowLevelMqttClient_Tests : BaseTestClass
@@ -17,11 +17,43 @@ using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
{
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
@@ -47,10 +79,12 @@ namespace MQTTnet.Tests.Client
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
@@ -162,7 +196,7 @@ namespace MQTTnet.Tests.Client
.Build();
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());
@@ -197,7 +231,7 @@ namespace MQTTnet.Tests.Client
{
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 connected = GetConnectedTask(managedClient);
@@ -224,7 +258,7 @@ namespace MQTTnet.Tests.Client
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 storage = new ManagedMqttClientTestStorage();
@@ -329,13 +363,7 @@ namespace MQTTnet.Tests.Client
await testEnvironment.StartServer().ConfigureAwait(false);
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
await sendingClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "topic",
Payload = new byte[] { 1 },
Retain = true
});
await sendingClient.PublishStringAsync("topic", "A", retain: true);
// Wait a bit for the retained message to be available
await LongTestDelay();
@@ -357,18 +385,15 @@ namespace MQTTnet.Tests.Client
await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync();
await LongTestDelay();
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
await LongTestDelay();
// After reconnect and then some delay, the retained message must not be received,
@@ -377,7 +402,6 @@ namespace MQTTnet.Tests.Client
// Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic");
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count);
@@ -397,7 +421,7 @@ namespace MQTTnet.Tests.Client
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
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;
@@ -430,7 +454,7 @@ namespace MQTTnet.Tests.Client
//wait a bit for the subscription to become established
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);
@@ -447,7 +471,7 @@ namespace MQTTnet.Tests.Client
}
}
async Task<ManagedMqttClient> CreateManagedClientAsync(
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment,
IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null,
@@ -464,7 +488,7 @@ namespace MQTTnet.Tests.Client
// at connection check intervals
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);
@@ -481,7 +505,7 @@ namespace MQTTnet.Tests.Client
/// name="managedClient" />
/// has connected
/// </summary>
Task GetConnectedTask(ManagedMqttClient managedClient)
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
{
var connected = new TaskCompletionSource<bool>();
@@ -503,7 +527,7 @@ namespace MQTTnet.Tests.Client
/// name="expectedNumberOfMessages" />
/// have been received
/// </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 result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -0,0 +1,222 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Connection_Tests : BaseTestClass
{
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
}
}
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
public async Task ConnectTimeout_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
var disconnectHandlerCalled = false;
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
Assert.Fail("Must fail!");
}
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
}
await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
}
}
[TestMethod]
public async Task Disconnect_Clean()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
}
[TestMethod]
public async Task Disconnect_Clean_With_Custom_Reason()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
}
}
[TestMethod]
public async Task Disconnect_Clean_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += args =>
{
eventArgs = args;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count);
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
}
}
[TestMethod]
public async Task Return_Non_Success()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ResponseUserProperties = new List<MqttUserProperty>
{
new MqttUserProperty("Property", "Value")
};
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
Assert.IsNotNull(response);
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
Assert.AreEqual(response.UserProperties[0].Name, "Property");
Assert.AreEqual(response.UserProperties[0].Value, "Value");
}
}
[TestMethod]
public async Task Throw_Proper_Exception_When_Not_Connected()
{
try
{
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
}
}
catch (MqttCommunicationException exception)
{
if (exception.Message == "The client is not connected.")
{
return;
}
}
Assert.Fail();
}
}
}
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Tests : BaseTestClass
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
public async Task Connect_Disconnect_Connect()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
}
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
using (var testEnvironment = CreateTestEnvironment())
{
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]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
[ExpectedException(typeof(InvalidOperationException))]
public async Task Connect_Multiple_Times_Should_Fail()
{
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())
using (var testEnvironment = CreateTestEnvironment())
{
var disconnectHandlerCalled = false;
try
{
client.DisconnectedAsync += args =>
{
disconnectHandlerCalled = true;
return CompletedTask.Instance;
};
await testEnvironment.StartServer();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
Assert.Fail("Must fail!");
}
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);
await client.ConnectAsync(clientOptions);
await client.ConnectAsync(clientOptions);
}
}
@@ -322,7 +296,7 @@ namespace MQTTnet.Tests.Client
Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(null, receivedMessage.Payload);
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
}
}
@@ -533,7 +507,7 @@ namespace MQTTnet.Tests.Client
client2.ApplicationMessageReceivedAsync += e =>
{
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
return CompletedTask.Instance;
};
@@ -6,9 +6,8 @@ using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Extensions;
namespace MQTTnet.Tests.Client
namespace MQTTnet.Tests.Clients
{
[TestClass]
public class MqttClientOptionsBuilder_Tests
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
var packets = new List<string>();
mqttClient.InspectPackage += eventArgs =>
mqttClient.InspectPacketAsync += eventArgs =>
{
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
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}");
}
}
}
}
@@ -3,71 +3,125 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Tests.Mockups;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests
namespace MQTTnet.Tests.Extensions
{
[TestClass]
public sealed class RPC_Tests : BaseTestClass
public sealed class Rpc_Tests : BaseTestClass
{
[TestMethod]
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod]
public async Task Execute_Success_Parameters_Propagated_Correctly()
{
var paramValue = "123";
var parameters = new Dictionary<string, object>
{
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" },
};
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}");
responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder()
.WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod]
public Task Execute_Success_With_QoS_0()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_1()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_2()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod]
public Task Execute_Success_With_QoS_1()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
}
[TestMethod]
public Task Execute_Success_With_QoS_2()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
}
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
{
@@ -90,6 +144,31 @@ namespace MQTTnet.Tests
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await CompletedTask.Instance;
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_With_Custom_Topic_Names()
@@ -98,14 +177,24 @@ namespace MQTTnet.Tests
{
await testEnvironment.StartServer();
var requestSender = await testEnvironment.ConnectClient();
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
[TestMethod]
public void Use_Factory()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
var rpcClient = factory.CreateMqttRpcClient(client);
Assert.IsNotNull(rpcClient);
}
}
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
{
using (var testEnvironment = CreateTestEnvironment())
@@ -151,57 +240,6 @@ namespace MQTTnet.Tests
}
}
[TestMethod]
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient();
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
responseSender.ApplicationMessageReceivedAsync += async e =>
{
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await CompletedTask.Instance;
};
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
}
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
@@ -213,5 +251,32 @@ namespace MQTTnet.Tests
};
}
}
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
};
}
}
}
}
}
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Tests.Helpers;
namespace MQTTnet.Tests.Extensions
{
[TestClass]
public sealed class WebSocket4Net_Tests
{
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_Failed_With_Invalid_Server()
{
var factory = new MqttFactory().UseWebSocket4Net();
using (var client = factory.CreateMqttClient())
{
var options = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("ws://a.b/mqtt")).WithTimeout(TimeSpan.FromSeconds(2)).Build();
await client.ConnectAsync(options).ConfigureAwait(false);
}
}
[TestMethod]
public void Use_Correct_Adapter()
{
// Should not throw exception
var factory = new MqttFactory().UseWebSocket4Net();
using (var client = factory.CreateMqttClient())
{
var adapterFactory = client.GetFieldValue("_adapterFactory");
Assert.IsInstanceOfType(adapterFactory, typeof(WebSocket4NetMqttClientAdapterFactory));
}
}
[TestMethod]
public void Use_WebSocket4Net()
{
// Should not throw exception
new MqttFactory().UseWebSocket4Net();
}
}
}
@@ -0,0 +1,310 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter
{
[TestClass]
public sealed class MqttBufferReader_Tests
{
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
public void Fire_Exception_If_Not_Enough_Data()
{
var buffer = new byte[] { 0, 1, 2 };
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, 3);
// 1 byte is missing.
reader.ReadFourByteInteger();
}
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, 3);
// 1 byte is missing.
reader.ReadFourByteInteger();
}
[TestMethod]
public void Is_EndOfStream_Without_Buffer()
{
var reader = new MqttBufferReader();
Assert.IsTrue(reader.EndOfStream);
Assert.AreEqual(0, reader.BytesLeft);
}
[TestMethod]
public void Read_Remaining_Data_From_Larger_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
// The used buffer contains more data than used!
reader.SetBuffer(buffer, 0, 5);
// This should only read 5 bytes even if more data is in the buffer
// due to custom bounds.
var remainingData = reader.ReadRemainingData();
Assert.IsTrue(reader.EndOfStream);
Assert.AreEqual(0, reader.BytesLeft);
Assert.AreEqual(5, remainingData.Length);
}
[TestMethod]
public void Read_Various_Positions_and_Offsets()
{
const int NumBufferElements = 1000;
const int NumTestSequences = 10000;
const string TestString = "The quick brown fox jumps over the lazy dog.";
var rndSeed = Environment.TickCount;
// rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
var rnd = new Random(rndSeed);
// rndSeed is printed when an assert fails
var bufferElements = new List<ElementReference>();
byte[] elementBuffer;
// Fill a buffer with strings or numbers in big endian format
using (var ms = new MemoryStream())
{
using (var bw = new BinaryWriter(ms))
{
var bufferOffset = 0;
for (var i = 0; i < NumBufferElements; i++)
{
var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
byte[] elementBytes = null;
uint elementNumberValue = 0; // all element number values fit into uint
string elementStringValue = null;
var elementSize = 0;
var alreadyBigEndian = false;
switch (nextElementType)
{
case ElementReference.BufferElementType.Byte:
var byteValue = (byte)i;
elementNumberValue = byteValue;
elementSize = sizeof(byte);
elementBytes = new byte[1] { byteValue };
alreadyBigEndian = true; // nothing to swap
break;
case ElementReference.BufferElementType.TwoByteInt:
var ushortValue = (ushort)i;
elementNumberValue = ushortValue;
elementSize = sizeof(ushort);
elementBytes = BitConverter.GetBytes(ushortValue);
break;
case ElementReference.BufferElementType.FourByteInt:
var uintValue = (uint)i;
elementNumberValue = uintValue;
elementSize = sizeof(uint);
elementBytes = BitConverter.GetBytes(uintValue);
break;
case ElementReference.BufferElementType.VariableSizeInt:
{
elementNumberValue = (uint)i;
var writer = new MqttBufferWriter(4, 4);
writer.WriteVariableByteInteger(elementNumberValue);
elementSize = writer.Length;
elementBytes = new byte[elementSize];
var buffer = writer.GetBuffer();
Array.Copy(buffer, elementBytes, elementSize);
alreadyBigEndian = true; // nothing to swap
}
break;
case ElementReference.BufferElementType.String:
{
var stringLen = rnd.Next(TestString.Length);
elementStringValue = TestString.Substring(0, stringLen); // could be empty
var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
writer.WriteString(elementStringValue);
elementSize = writer.Length;
elementBytes = new byte[elementSize];
var buffer = writer.GetBuffer();
Array.Copy(buffer, elementBytes, elementSize);
alreadyBigEndian = true; // nothing to swap
}
break;
}
if (BitConverter.IsLittleEndian && !alreadyBigEndian)
{
Array.Reverse(elementBytes);
}
bw.Write(elementBytes);
bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
bufferOffset += elementSize;
}
elementBuffer = ms.ToArray();
}
}
for (var i = 0; i < NumTestSequences; i++)
{
var firstElementIndex = rnd.Next(NumBufferElements - 1);
// segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
var lastElementIndex = firstElementIndex + elementCount - 1;
// Use element index to get buffer offsets
var lastElement = bufferElements[lastElementIndex];
var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
var segmentLength = segmentEndPosition - segmentStartPosition;
var reader = new MqttBufferReader();
reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
// read all elements in the buffer segment; values should be as expected
for (var n = 0; n < elementCount; n++)
{
var element = bufferElements[firstElementIndex + n];
uint elementNumberValue = 0;
string elementStringValue = null;
var expectedPosition = element.BufferOffset - segmentStartPosition;
Assert.AreEqual(expectedPosition, reader.Position);
switch (element.Type)
{
case ElementReference.BufferElementType.Byte:
{
elementNumberValue = reader.ReadByte();
}
break;
case ElementReference.BufferElementType.TwoByteInt:
{
elementNumberValue = reader.ReadTwoByteInteger();
}
break;
case ElementReference.BufferElementType.FourByteInt:
{
elementNumberValue = reader.ReadFourByteInteger();
}
break;
case ElementReference.BufferElementType.VariableSizeInt:
{
elementNumberValue = reader.ReadVariableByteInteger();
}
break;
case ElementReference.BufferElementType.String:
{
elementStringValue = reader.ReadString();
}
break;
}
if (elementStringValue != null)
{
Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
}
else
{
Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
}
}
// confirm end of stream
Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
}
}
[TestMethod]
public void Report_Correct_Length_For_Full_Buffer()
{
var buffer = new byte[] { 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, 5);
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
[TestMethod]
public void Report_Correct_Length_For_Partial_End_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
// The used buffer contains more data than used!
reader.SetBuffer(buffer, 5, 5);
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
[TestMethod]
public void Report_Correct_Length_For_Partial_Start_Buffer()
{
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var reader = new MqttBufferReader();
// The used buffer contains more data than used!
reader.SetBuffer(buffer, 0, 5);
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
// Helper class to build up a reference to elements of various types in a buffer
class ElementReference
{
public enum BufferElementType
{
Byte,
TwoByteInt,
FourByteInt,
VariableSizeInt,
String
}
public const int NumBufferElementTypes = 5;
public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
{
Type = type;
Size = size;
NumberValue = numberValue;
StringValue = stringValue;
BufferOffset = bufferOffset;
}
public int BufferOffset { get; }
public uint NumberValue { get; }
public int Size { get; }
public string StringValue { get; }
public BufferElementType Type { get; }
}
}
}
@@ -15,7 +15,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Helpers;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Formatter
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"),
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C"
};
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
var publishPacket = new MqttPublishPacket
{
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
Payload = payload
PayloadSegment = new ArraySegment<byte>(payload)
};
var serializationHelper = new MqttPacketSerializationHelper();
var buffer = serializationHelper.Encode(publishPacket);
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNotNull(publishPacketCopy);
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.
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
buffer = serializationHelper.Encode(publishPacket);
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
Assert.IsNotNull(publishPacketCopy2);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
}
[TestMethod]
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"),
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C"
};
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
using (var adapter = new MqttChannelAdapter(
channel,
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
null,
new MqttNetEventLogger()))
{
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] 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();
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
{
var writer = bufferWriter ?? WriterFactory();
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
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();
}
}
@@ -2,7 +2,9 @@
// 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.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
@@ -76,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
}
[TestMethod]
[TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310()
{
var connAckPacket = new MqttConnAckPacket
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"),
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic",
ResponseTopic = "/Response",
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
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.Topic, deserialized.Topic);
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.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter;
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123,
Dup = true,
Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"),
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic",
ResponseTopic = "/Response",
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
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.Topic, deserialized.Topic);
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter
@@ -47,23 +48,32 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(1234U, 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]
public void Write_And_Read_Multiple_Times()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinaryData(new byte[1234]);
writer.WriteBinary(new byte[1234]);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(new byte[48]);
writer.WriteBinary(new byte[48]);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(new byte[3]);
writer.WriteBinary(new byte[3]);
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Helpers
{
public static class MqttClientExtensions
{
public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
return new TestApplicationMessageReceivedHandler(client);
}
}
}
@@ -5,7 +5,7 @@
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Extensions
namespace MQTTnet.Tests.Helpers
{
public static class MqttPacketWriterExtensions
{
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Reflection;
namespace MQTTnet.Tests.Helpers
{
public static class ReflectionExtensions
{
public static object GetFieldValue(this object source, string fieldName)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null)
{
throw new ArgumentException($"Field {fieldName} not found.");
}
return field.GetValue(source);
}
}
}
+4 -5
View File
@@ -1,27 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
</ItemGroup>
</Project>
+2 -2
View File
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
var c2 = await testEnvironment.ConnectClient(clientOptions);
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent.
await Task.Delay(1000);
await LongTestDelay();
Assert.AreEqual(1, receivedMessagesCount);
}
@@ -27,6 +27,17 @@ namespace MQTTnet.Tests.Mockups
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
}
public int Count
{
get
{
lock (_receivedEventArgs)
{
return _receivedEventArgs.Count;
}
}
}
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
{
get
@@ -40,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
public void AssertReceivedCountEquals(int expectedCount)
{
lock (_receivedEventArgs)
{
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
}
Assert.AreEqual(expectedCount, Count);
}
public string GeneratePayloadSequence()
+44 -34
View File
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -24,10 +25,9 @@ namespace MQTTnet.Tests.Mockups
public sealed class TestEnvironment : IDisposable
{
readonly List<string> _clientErrors = new List<string>();
readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly ConcurrentBag<IMqttClient> _clients = new ConcurrentBag<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>();
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly ConcurrentBag<ILowLevelMqttClient> _lowLevelClients = new ConcurrentBag<ILowLevelMqttClient>();
readonly MqttProtocolVersion _protocolVersion;
readonly List<string> _serverErrors = new List<string>();
@@ -65,9 +65,12 @@ namespace MQTTnet.Tests.Mockups
if (e.LogMessage.Level == MqttNetLogLevel.Error)
{
lock (_clientErrors)
if (!IgnoreClientLogErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
lock (_clientErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
}
}
}
};
@@ -212,41 +215,45 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient()
{
lock (_clients)
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e =>
{
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
var client = Factory.CreateMqttClient(logger);
_clients.Add(client);
client.ConnectingAsync += e =>
if (TestContext != null)
{
if (TestContext != null)
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
return CompletedTask.Instance;
};
_clients.Add(client);
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()
{
lock (_clients)
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
return client;
}
return client;
}
public MqttServer CreateServer(MqttServerOptions options)
@@ -373,13 +380,16 @@ namespace MQTTnet.Tests.Mockups
}
}
lock (_clientErrors)
if (!IgnoreClientLogErrors)
{
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
lock (_clientErrors)
{
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
if (_clientErrors.Count > 0)
{
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
}
}
}
}
@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Protocol;
@@ -11,7 +12,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttApplicationMessageBuilder_Tests
public sealed class MqttApplicationMessageBuilder_Tests
{
[TestMethod]
public void CreateApplicationMessage_TopicOnly()
@@ -29,7 +30,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("xyz", message.Topic);
Assert.IsFalse(message.Retain);
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]
@@ -41,7 +42,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("123", message.Topic);
Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
}
[TestMethod]
@@ -0,0 +1,48 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttApplicationMessageTest
{
[TestMethod]
public void PayloadSegment()
{
var message = new MqttApplicationMessage();
Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsFalse(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
}
}
}
@@ -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 adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
writer.WriteString("1234567890");
Assert.AreEqual(10 + 2, writer.Length);
writer.WriteBinaryData(new byte[300]);
writer.WriteBinary(new byte[300]);
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);
}
}
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttTopicValidator_Tests
public sealed class MqttTopicValidator_Tests
{
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Plus()
public void Invalid_Topic_Empty()
{
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
MqttTopicValidator.ThrowIfInvalid(string.Empty);
}
[TestMethod]
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
[TestMethod]
[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");
}
}
}
}
+27 -6
View File
@@ -8,27 +8,48 @@ using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
public class Protocol_Tests
public sealed class Protocol_Tests
{
[TestMethod]
public void Encode_Four_Byte_Integer()
{
var writer = new MqttBufferWriter(4, 4);
for (uint value = 0; value < 268435455; value++)
{
writer.WriteVariableByteInteger(value);
var buffer = writer.GetBuffer();
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, writer.Length);
var checkValue = reader.ReadVariableByteInteger();
Assert.AreEqual(value, checkValue);
writer.Reset(0);
}
}
[TestMethod]
public void Encode_Two_Byte_Integer()
{
var writer = new MqttBufferWriter(2, 2);
for (ushort value = 0; value < ushort.MaxValue; value++)
{
writer.WriteTwoByteInteger(value);
var buffer = writer.GetBuffer();
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, writer.Length);
var checkValue = reader.ReadTwoByteInteger();
Assert.AreEqual(value, checkValue);
writer.Reset(0);
}
}
}
}
}

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