Compare commits

...
80 Commits
Author SHA1 Message Date
Christian Kratky ba62ca7306 Update docs. 2018-10-01 21:36:15 +02:00
Christian Kratky 56a1530cee Add a method for clearing all retained messages at the server. 2018-09-29 11:49:02 +02:00
Christian Kratky 699cd5fc23 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-09-28 22:51:27 +02:00
Christian Kratky 381c59d1e5 Update readme. 2018-09-28 22:51:16 +02:00
Christian Kratky 70f4dc9e10 Change log level of retained message changes. 2018-09-28 22:51:05 +02:00
Christian 0367ad31f1 Merge pull request #422 from ifit/client-cancel-task
Client cancel task
2018-09-28 22:49:11 +02:00
Christian 5c6bcdbca6 Merge branch 'develop' into client-cancel-task 2018-09-28 22:49:00 +02:00
Andre Crabb 400c0f05ad * Adds false param to Cancel call. 2018-09-28 14:07:04 -04:00
Andre Crabb 00df7c9b7b * More change tabs to spaces. 2018-09-28 11:01:07 -04:00
Andre Crabb a272ed7daa * Change tabs to spaces. 2018-09-28 11:00:18 -04:00
Andre Crabb b2e7a2e370 Change from logging Info to Verbose 2018-09-21 15:44:31 -04:00
Andre Crabb 39bc70cbf0 * Cancel the TokenSource when disposing or nulling it out for the MqttClient 2018-09-20 11:08:33 -04:00
Christian Kratky 682298b69f Fix issue in pending messages handling for managed client. 2018-09-13 09:29:26 +02:00
Christian Kratky 028a39ec25 Update docs. 2018-09-13 09:19:15 +02:00
Christian Kratky 3b104049b4 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-08-25 17:33:58 +02:00
Christian dd83a360b4 Merge pull request #402 from JanEggers/AspNetMqttServerOptionsBuilder
added AspNetMqttServerOptionsBuilder
2018-08-25 16:56:34 +02:00
JanEggers 976c62d9c2 added back the old function and added new overload 2018-08-25 12:08:42 +02:00
JanEggers 4cc9f12a03 added AspNetMqttServerOptionsBuilder 2018-08-21 21:32:03 +02:00
Christian Kratky 0b5a751c56 Add all methods of the factory to the factory interfaces. 2018-08-15 17:48:28 +02:00
Christian Kratky cdb05e5894 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-08-04 12:30:21 +02:00
Christian Kratky 6c08b5caf3 Update docs. 2018-08-04 12:30:08 +02:00
Christian d5b53f3712 Merge pull request #384 from cstichlberger/managed_client_fix_unsubscription_overtaking_subscription
Prevent unsubscription overtaking later subscription
2018-08-04 12:28:04 +02:00
Christoph Stichlberger 80fbb2f0b2 Prevent unsubscription overtaking later subscription 2018-08-02 16:25:14 +02:00
Christian Kratky b4f90decc7 Fix unit tests to allow release builds. 2018-07-27 17:20:19 +02:00
Christian Kratky 61b324043a Refactoring 2018-07-26 20:02:11 +02:00
Christian Kratky 2ced921008 Refactoring. 2018-07-26 14:48:40 +02:00
Christian 1e2fee0f33 Merge pull request #343 from JanEggers/ServerShutdown
Server shutdown
2018-07-26 14:45:44 +02:00
Christian ac36c9c406 Merge branch 'develop' into ServerShutdown 2018-07-26 14:45:25 +02:00
Christian Kratky f70c79aaf9 Refactor options. 2018-07-26 11:34:24 +02:00
Christian Kratky 0839fa5757 Update docs. 2018-07-26 10:43:40 +02:00
Christian Kratky 60bc4ebcee Refactor proxy usage and support. Refactor TLS parameter usage. 2018-07-26 10:35:52 +02:00
Christian 65e239652d Merge pull request #364 from PitySOFT/webproxy
Add proxy settings for WebSocket connections for .NET 451 and 461 and add option to specify the SSLProtocol and CertificateValidationCallback  in TLS options, SSLProtocol default is TLS1.2
2018-07-26 10:04:52 +02:00
Christian Kratky 6b9015a928 Fix order of ClientConnected and ClientDisconnected events. 2018-07-25 20:18:27 +02:00
Christian Kratky a740c38ee6 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-07-24 14:06:18 +02:00
Christian Kratky 603639126e Fix issue in processing of server messages. 2018-07-24 13:49:23 +02:00
JanEggers 043cfdb3f9 fixed deadlock and object disposed exception 2018-07-23 17:58:29 +02:00
Christian b0da4b2b2d Merge pull request #372 from jenscski/LogFix
Small fix for inconsistent client name in log
2018-07-22 21:10:05 +02:00
Christian 5402859a30 Merge pull request #368 from jenscski/ClientKeepAliveMonitor
Fix pause/resume of ClientKeepAliveMonitor
2018-07-22 21:09:21 +02:00
Jens-Christian Skibakk 2b10200101 Small fix for inconsistent client name in log 2018-07-22 20:09:37 +02:00
Jens-Christian Skibakk 55e8927ab7 Fix pause/resume of ClientKeepAliveMonitor 2018-07-20 14:13:21 +02:00
JanEggers d30fe61264 fixed missing waits 2018-07-19 21:13:29 +02:00
JanEggers f7ea2d29e1 prove its broken 2018-07-19 21:13:28 +02:00
JanEggers e6cfef5295 fixed disconnect to be triggered just once 2018-07-19 21:13:28 +02:00
JanEggers 0dea9e6a2b changed waiting condition 2018-07-19 21:13:28 +02:00
JanEggers ae80de26ec addressed feedback 2018-07-19 21:13:28 +02:00
JanEggers 53bd2a0960 fixed blocking code in networkstrem and reduced sleep in tests 2018-07-19 21:13:28 +02:00
JanEggers b55f5ff298 event is triggered but something is blocking 2018-07-19 21:13:27 +02:00
JanEggers 21048b51ae prove there is something wrong 2018-07-19 21:13:27 +02:00
Christian Kratky 773fd09166 Merge branch 'master' into develop 2018-07-19 21:02:37 +02:00
Christian 57532a290a Merge pull request #365 from jenscski/DotnetCliBuild
Skip build uap10.0 when using dotnet cli
2018-07-19 18:58:48 +02:00
Jens-Christian Skibakk 740a35b018 Skip build uap10.0 when using dotnet cli 2018-07-18 19:54:07 +02:00
Marius Ruginosu 6e995230a7 Created overload methods for WithTls default values 2018-07-18 16:54:19 +03:00
Marius Ruginosu 65a8b091b9 Add option to specify the SSLProtocol and CertificateValidationCallback in TLS options, SSLProtocol default is TLS1.2 2018-07-18 12:19:09 +03:00
Marius Ruginosu fa34908d82 Add proxy settings for WebSocket connections for .NET 451 and 461 2018-07-18 10:51:45 +03:00
Christian Kratky b09eb1213c Refactor logging. 2018-07-08 22:04:06 +02:00
Christian Kratky 23ae8ab3b5 Fix unit test. 2018-07-08 14:10:25 +02:00
Christian Kratky 0bdb7e9e44 Merge branch 'develop' 2018-07-08 14:02:35 +02:00
Christian Kratky 54e3cd7cb7 Update nuspecs. 2018-07-08 14:01:47 +02:00
Christian Kratky 16ad07cfc9 Add wrapper for WebSocket4Net. 2018-07-08 10:34:46 +02:00
Christian Kratky dee0adcd8c Add new overloads. 2018-07-07 22:46:07 +02:00
Christian Kratky acbf1226ab Fix a bug in the keep alive monitor. 2018-07-07 21:22:08 +02:00
Christian Kratky f002744d87 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-07-07 21:03:36 +02:00
Christian Kratky 9efee7af21 Refactoring. 2018-07-07 21:03:31 +02:00
Christian 9814b10496 Merge pull request #355 from JanEggers/ConnectionHandlerWithWebsockets
Connection handler with websockets
2018-07-07 21:00:32 +02:00
JanEggers 978694b3c0 set binary mode 2018-07-07 10:52:24 +02:00
JanEggers bc6f155f12 using connectionhandlers with websockets 2018-07-07 10:42:33 +02:00
Christian Kratky d494c2104b Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-07-05 21:20:14 +02:00
Christian Kratky 8035c6859f Update docs. 2018-07-05 21:20:08 +02:00
Christian Kratky 2f10295022 Fix message handling for UWP. 2018-07-05 21:20:01 +02:00
Christian e5f32730a7 Merge pull request #353 from JanEggers/hideoverloadthatdoesnotprovideoptions
hide overload that does not provide options
2018-07-05 21:13:35 +02:00
JanEggers cd257b4d25 hide overload that does not provide options 2018-07-05 20:31:01 +02:00
Christian Kratky ce534baa75 Move the connection check interval of the ManagedClient to the options. 2018-07-04 22:20:24 +02:00
Christian Kratky 2b420b8e66 Add support for disabling server endpoints (ipv4 or ipv6) 2018-07-04 22:20:02 +02:00
Christian Kratky 35300abfb5 Fix typo. 2018-07-04 22:03:36 +02:00
Christian Kratky 3d0ecc05ff Fix bug which prevents receiving large packets for UWP. 2018-07-04 22:03:19 +02:00
Christian Kratky bfb6ec8856 Update UWP test app. 2018-07-04 22:02:42 +02:00
Christian Kratky bf1d682a85 Update libs 2018-07-04 22:00:48 +02:00
Christian Kratky 7d9326a147 Extend MqttPacketReader constructor to allow more unit tests. 2018-07-04 22:00:34 +02:00
Christian Kratky 7fd09009e7 Update docs. 2018-07-04 21:58:00 +02:00
Christian Kratky 9fa5e2dc1f Add the adapter Endpoint to the connection validator. 2018-07-04 21:57:51 +02:00
58 changed files with 1166 additions and 360 deletions
+2 -2
View File
@@ -10,12 +10,12 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is a support library to integrate MQTTnet into AspNetCore.</description> <description>This is a support library to integrate MQTTnet into AspNetCore.</description>
<releaseNotes>* Updated to MQTTnet 2.8.0. <releaseNotes>* For more release notes please check the MQTTnet release notes.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2018</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
<dependency id="MQTTnet" version="2.8.0" /> <dependency id="MQTTnet" version="2.8.3" />
<dependency id="Microsoft.AspNetCore.Connections.Abstractions" version="2.1.0" /> <dependency id="Microsoft.AspNetCore.Connections.Abstractions" version="2.1.0" />
<dependency id="Microsoft.AspNetCore.WebSockets" version="2.0.1" /> <dependency id="Microsoft.AspNetCore.WebSockets" version="2.0.1" />
<dependency id="Microsoft.Extensions.Hosting.Abstractions" version="2.0.1" /> <dependency id="Microsoft.Extensions.Hosting.Abstractions" version="2.0.1" />
@@ -10,12 +10,12 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</description> <description>This is an extension library which provides a managed MQTT client with additional features using MQTTnet.</description>
<releaseNotes>* Updated to MQTTnet 2.8.0. <releaseNotes>* For more release notes please check the MQTTnet release notes.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2018</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
<dependency id="MQTTnet" version="2.8.0" /> <dependency id="MQTTnet" version="2.8.3" />
</dependencies> </dependencies>
</metadata> </metadata>
@@ -33,6 +33,9 @@
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\net452\MQTTnet.Extensions.ManagedClient.*" target="lib\net452\"/> <file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\net452\MQTTnet.Extensions.ManagedClient.*" target="lib\net452\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net461\"/> <file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net461\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net462\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net470\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net471\"/>
<file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net472\"/> <file src="..\Source\MQTTnet.Extensions.ManagedClient\bin\Release\netstandard2.0\MQTTnet.Extensions.ManagedClient.*" target="lib\net472\"/>
</files> </files>
</package> </package>
+5 -2
View File
@@ -10,12 +10,12 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</description> <description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
<releaseNotes>* Updated to MQTTnet 2.8.0. <releaseNotes>* For more release notes please check the MQTTnet release notes.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2018</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
<dependency id="MQTTnet" version="2.8.0" /> <dependency id="MQTTnet" version="2.8.3" />
</dependencies> </dependencies>
</metadata> </metadata>
@@ -34,5 +34,8 @@
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/> <file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net462\"/> <file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net462\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net470\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net471\"/>
<file src="..\Source\MQTTnet.Extensions.Rpc\bin\Release\netstandard2.0\MQTTnet.Extensions.Rpc.*" target="lib\net472\"/>
</files> </files>
</package> </package>
+7 -27
View File
@@ -10,33 +10,10 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes> ** MQTTnet is now available at Open Collective for donations (https://opencollective.com/mqttnet). ** <releaseNotes>* [Core] Added all factory methods to the factory interface.
* [Core] Performance optimizations. * [Core] Fixed an issue with cancellation token handling (thanks to @acrabb).
* [Core] Due to performance reasons the timestamp of log messages is now in UTC format. * [Server] Added a new overload for configuring the ASP.net integration (thanks to @JanEggers).
* [Core] Added several packet validations. * [Server] Added a method for clearing all retained messages.
* [Core] Log messages now contain the complete source path including parent components.
* [Core] The adapter now has an _Endpoint_ definition as string containing remote IP and port.
* [Client] Received messages are now processed completely in the worker thread without creating new Tasks.
* [Client] Fixed wrong calculation for sending keep alive packets (thanks to @cstichlberger)
* [Client] A clean disconnect (via DisconnectAsync) will no longer throw an exception.
* [Client] Added new overloads for quick message publishing.
* [ManagedClient] The managed client is moved to a separate nuget package.
* [ManagedClient] Added an own message format with extended properties like ID (BREAKING CHANGE).
* [ManagedClient] Fixed a loading issue of stored application messages (thanks to @JTrotta).
* [ManagedClient] Added a new event which is fired when a synchronization of the subscriptions has failed.
* [ManagedClient] Added a new event which is fired when a connection attempt has failed.
* [ManagedClient] Exposed a new property which provides the count of not published messages (pending messages count).
* [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot).
* [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client.
* [Server] The pending messages queue per client is now limited to 250 messages. Overflow strategy and count can be changed via options (thanks to @VladimirAkopyan)
* [Server] Keep alive checking is now suspended while large packages are being received (and thus the client is connected). Keep alive checking continues after a large packet is received completely.
* [Server] Rewritten the _ConnectedClients_ API and added new features for disconnecting and Endpoint information (IP etc.).
* [Server] Added settings for disabling persistent sessions and defining a max pending messages queue size per session.
* [Server] Persistent sessions are disabled by default (BREAKING CHANGE!).
* [Server] Added a new interceptor which is invoked before a new message is added to the client queue.
* [Server] Added support for Linux servers by dividing IPv4 and IPv6 support and adding new options (BREAKING CHANGE!).
* [Server] Gracefully closed connections are no longer reported as warnings.
* [Server] Added new overloads for initializing the ASP.NET Core integration.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2018</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
@@ -77,6 +54,9 @@
<file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/> <file src="..\Source\MQTTnet\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net461\"/> <file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net461\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net462\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net470\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net471\"/>
<file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net472\"/> <file src="..\Source\MQTTnet\bin\Release\netstandard2.0\MQTTnet.*" target="lib\net472\"/>
</files> </files>
</package> </package>
+5 -2
View File
@@ -1,9 +1,11 @@
<p align="center"> <p align="center">
<img src="https://github.com/chkr1011/MQTTnet/blob/master/Images/Logo_128x128.png?raw=true" width="128"> <img src="https://github.com/chkr1011/MQTTnet/blob/master/Images/Logo_128x128.png?raw=true" width="128">
<br/>
<br/>
</p> </p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![Build status](https://ci.appveyor.com/api/projects/status/ycit86voxfevm2aa/branch/master?svg=true)](https://ci.appveyor.com/project/chkr1011/mqttnet/branch/develop) [![Build status](https://ci.appveyor.com/api/projects/status/ycit86voxfevm2aa/branch/master?svg=true)](https://ci.appveyor.com/project/chkr1011/mqttnet)
[![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/) [![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/)
[![OpenCollective](https://opencollective.com/mqttnet/backers/badge.svg)](https://opencollective.com/mqttnet) [![OpenCollective](https://opencollective.com/mqttnet/backers/badge.svg)](https://opencollective.com/mqttnet)
[![OpenCollective](https://opencollective.com/mqttnet/sponsors/badge.svg)](https://opencollective.com/mqttnet) [![OpenCollective](https://opencollective.com/mqttnet/sponsors/badge.svg)](https://opencollective.com/mqttnet)
@@ -83,9 +85,10 @@ This project also listed at Open Collective (https://opencollective.com/mqttnet)
This library is used in the following projects: This library is used in the following projects:
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>)
* MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>) * MQTT Client Rx (Wrapper for Reactive Extensions, <https://github.com/1iveowl/MQTTClient.rx>)
* MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8)) * MQTT Tester (MQTT client test app for [Android](https://play.google.com/store/apps/details?id=com.liveowl.mqtttester) and [iOS](https://itunes.apple.com/us/app/mqtt-tester/id1278621826?mt=8))
* HA4IoT (Open Source Home Automation system for .NET, <https://github.com/chkr1011/HA4IoT>) * Wirehome.Core (Open Source Home Automation system for .NET Core, <https://github.com/chkr1011/Wirehome.Core>)
If you use this library and want to see your project here please let me know. If you use this library and want to see your project here please let me know.
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Linq; using System.Linq;
using MQTTnet.Server; using MQTTnet.Server;
using System.Collections.Generic;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
@@ -23,10 +24,7 @@ namespace MQTTnet.AspNetCore
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)) if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
{ {
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc. subProtocol = SelectSubProtocol(requestedSubProtocolValues);
subProtocol = requestedSubProtocolValues
.OrderByDescending(p => p.Length)
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
} }
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>(); var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
@@ -40,6 +38,14 @@ namespace MQTTnet.AspNetCore
return app; return app;
} }
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
{
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
return requestedSubProtocolValues
.OrderByDescending(p => p.Length)
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
}
public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<IMqttServer> configure) public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action<IMqttServer> configure)
{ {
var server = app.ApplicationServices.GetRequiredService<IMqttServer>(); var server = app.ApplicationServices.GetRequiredService<IMqttServer>();
@@ -0,0 +1,15 @@
using MQTTnet.Server;
using System;
namespace MQTTnet.AspNetCore
{
public class AspNetMqttServerOptionsBuilder : MqttServerOptionsBuilder
{
public AspNetMqttServerOptionsBuilder(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
public IServiceProvider ServiceProvider { get; }
}
}
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Serializer; using MQTTnet.Serializer;
using MQTTnet.Server; using MQTTnet.Server;
@@ -13,6 +14,14 @@ namespace MQTTnet.AspNetCore
public override async Task OnConnectedAsync(ConnectionContext connection) public override async Task OnConnectedAsync(ConnectionContext connection)
{ {
// required for websocket transport to work
var transferFormatFeature = connection.Features.Get<ITransferFormatFeature>();
if (transferFormatFeature != null)
{
transferFormatFeature.ActiveFormat = TransferFormat.Binary;
}
var serializer = new MqttPacketSerializer(); var serializer = new MqttPacketSerializer();
using (var adapter = new MqttConnectionContext(serializer, connection)) using (var adapter = new MqttConnectionContext(serializer, connection))
{ {
@@ -32,7 +32,8 @@ namespace MQTTnet.AspNetCore
} }
var bodySlice = copy.Slice(0, bodyLength); var bodySlice = copy.Slice(0, bodyLength);
packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(bodySlice.GetArray(), 0))); var buffer = bodySlice.GetArray();
packet = serializer.Deserialize(new ReceivedMqttPacket(fixedheader, new MqttPacketBodyReader(buffer, 0, buffer.Length)));
consumed = bodySlice.End; consumed = bodySlice.End;
observed = bodySlice.End; observed = bodySlice.End;
return true; return true;
@@ -20,14 +20,27 @@ namespace MQTTnet.AspNetCore
return services; return services;
} }
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure) public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
{ {
var builder = new MqttServerOptionsBuilder(); services.AddSingleton<IMqttServerOptions>(s => {
var builder = new MqttServerOptionsBuilder();
configure(builder);
return builder.Build();
});
configure(builder); services.AddHostedMqttServer();
services.AddSingleton<IMqttServerOptions>(builder.Build()); return services;
}
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{
services.AddSingleton<IMqttServerOptions>(s => {
var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
return builder.Build();
});
services.AddHostedMqttServer(); services.AddHostedMqttServer();
@@ -44,7 +57,7 @@ namespace MQTTnet.AspNetCore
return services; return services;
} }
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services) private static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
{ {
var logger = new MqttNetLogger(); var logger = new MqttNetLogger();
var childLogger = logger.CreateChildLogger(); var childLogger = logger.CreateChildLogger();
@@ -9,6 +9,8 @@ namespace MQTTnet.Extensions.ManagedClient
TimeSpan AutoReconnectDelay { get; } TimeSpan AutoReconnectDelay { get; }
TimeSpan ConnectionCheckInterval { get; }
IManagedMqttClientStorage Storage { get; } IManagedMqttClientStorage Storage { get; }
} }
} }
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
<Product /> <Product />
<Company /> <Company />
<Authors /> <Authors />
@@ -206,7 +206,7 @@ namespace MQTTnet.Extensions.ManagedClient
if (connectionState == ReconnectionResult.StillConnected) if (connectionState == ReconnectionResult.StillConnected)
{ {
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await Task.Delay(_options.ConnectionCheckInterval, cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -226,7 +226,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested && _mqttClient.IsConnected)
{ {
var message = _messageQueue.Take(cancellationToken); var message = _messageQueue.Take(cancellationToken);
if (message == null) if (message == null)
@@ -306,15 +306,15 @@ namespace MQTTnet.Extensions.ManagedClient
try try
{ {
if (subscriptions.Any())
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}
if (unsubscriptions.Any()) if (unsubscriptions.Any())
{ {
await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false); await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false);
} }
if (subscriptions.Any())
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -367,7 +367,6 @@ namespace MQTTnet.Extensions.ManagedClient
} }
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
_publishingCancellationToken = cts; _publishingCancellationToken = cts;
Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); Task.Factory.StartNew(() => PublishQueuedMessages(cts.Token), cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
@@ -9,6 +9,8 @@ namespace MQTTnet.Extensions.ManagedClient
public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5); public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan ConnectionCheckInterval { get; set; } = TimeSpan.FromSeconds(1);
public IManagedMqttClientStorage Storage { get; set; } public IManagedMqttClientStorage Storage { get; set; }
} }
} }
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
<Product /> <Product />
<Company /> <Company />
<Authors /> <Authors />
+1 -1
View File
@@ -3,7 +3,7 @@ _mqttClient.subscribe("MQTTnet.RPC/+/ping");
_mqttClient.subscribe("MQTTnet.RPC/+/do_something"); _mqttClient.subscribe("MQTTnet.RPC/+/do_something");
// It is not allowed to change the structure of the topic. Otherwise RPC will not work. So method names can be separated using // It is not allowed to change the structure of the topic. Otherwise RPC will not work. So method names can be separated using
// an _ or . but no +, # or . If it is required to distinguish between devices own rules can be defined like the following. // an _ or . but no +, # or /. If it is required to distinguish between devices own rules can be defined like the following.
_mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping"); _mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping");
_mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping"); _mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping");
_mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature"); _mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature");
@@ -0,0 +1,134 @@
using MQTTnet.Client;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Channel;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using WebSocket4Net;
namespace MQTTnet.TestApp.NetCore
{
public class WebSocket4NetMqttClientAdapterFactory : IMqttClientAdapterFactory
{
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (!(options.ChannelOptions is MqttClientWebSocketOptions))
{
throw new NotSupportedException("Only WebSocket connections are supported.");
}
return new MqttChannelAdapter(new WebSocket4NetMqttChannel(options), new MqttPacketSerializer(), logger);
}
private class WebSocket4NetMqttChannel : IMqttChannel
{
private readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
private readonly IMqttClientOptions _clientOptions;
private WebSocket4Net.WebSocket _webSocket;
public WebSocket4NetMqttChannel(IMqttClientOptions clientOptions)
{
_clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
}
public string Endpoint { get; } = "";
public Task ConnectAsync(CancellationToken cancellationToken)
{
var channelOptions = (MqttClientWebSocketOptions)_clientOptions.ChannelOptions;
var uri = "ws://" + channelOptions.Uri;
var sslProtocols = SslProtocols.None;
if (channelOptions.TlsOptions.UseTls)
{
uri = "wss://" + channelOptions.Uri;
sslProtocols = SslProtocols.Tls12;
}
var subProtocol = channelOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
_webSocket = new WebSocket4Net.WebSocket(uri, subProtocol, sslProtocols: sslProtocols);
_webSocket.DataReceived += OnDataReceived;
_webSocket.Open();
SpinWait.SpinUntil(() => _webSocket.State == WebSocketState.Open, _clientOptions.CommunicationTimeout);
return Task.FromResult(0);
}
public Task DisconnectAsync()
{
if (_webSocket != null)
{
_webSocket.DataReceived -= OnDataReceived;
_webSocket.Close();
SpinWait.SpinUntil(() => _webSocket.State == WebSocketState.Closed, _clientOptions.CommunicationTimeout);
}
_webSocket = null;
return Task.FromResult(0);
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var readBytes = 0;
while (count > 0 && !cancellationToken.IsCancellationRequested)
{
byte @byte;
if (readBytes == 0)
{
// Block until at lease one byte was received.
@byte = _receiveBuffer.Take(cancellationToken);
}
else
{
if (!_receiveBuffer.TryTake(out @byte))
{
return Task.FromResult(readBytes);
}
}
buffer[offset] = @byte;
offset++;
count--;
readBytes++;
}
return Task.FromResult(readBytes);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
_webSocket.Send(buffer, offset, count);
return Task.FromResult(0);
}
public void Dispose()
{
if (_webSocket != null)
{
_webSocket.DataReceived -= OnDataReceived;
_webSocket.Dispose();
}
}
private void OnDataReceived(object sender, WebSocket4Net.DataReceivedEventArgs e)
{
foreach (var @byte in e.Data)
{
_receiveBuffer.Add(@byte);
}
}
}
}
}
+16 -15
View File
@@ -97,13 +97,11 @@ namespace MQTTnet.Adapter
await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _writerSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try try
{ {
_logger.Verbose("TX >>> {0}", packet);
var packetData = PacketSerializer.Serialize(packet); var packetData = PacketSerializer.Serialize(packet);
await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetData.Array, packetData.Offset, packetData.Count, cancellationToken).ConfigureAwait(false);
PacketSerializer.FreeBuffer(); PacketSerializer.FreeBuffer();
_logger.Verbose("TX >>> {0}", packet);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -130,11 +128,11 @@ namespace MQTTnet.Adapter
if (timeout > TimeSpan.Zero) if (timeout > TimeSpan.Zero)
{ {
receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false); receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfterAsync(ReceiveAsync, timeout, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false); receivedMqttPacket = await ReceiveAsync(cancellationToken).ConfigureAwait(false);
} }
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
@@ -149,7 +147,7 @@ namespace MQTTnet.Adapter
} }
_logger.Verbose("RX <<< {0}", packet); _logger.Verbose("RX <<< {0}", packet);
return packet; return packet;
} }
catch (Exception exception) catch (Exception exception)
@@ -165,9 +163,9 @@ namespace MQTTnet.Adapter
return null; return null;
} }
private async Task<ReceivedMqttPacket> ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken) private async Task<ReceivedMqttPacket> ReceiveAsync(CancellationToken cancellationToken)
{ {
var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(channel, _fixedHeaderBuffer, _singleByteBuffer, cancellationToken).ConfigureAwait(false); var fixedHeader = await MqttPacketReader.ReadFixedHeaderAsync(_channel, _fixedHeaderBuffer, _singleByteBuffer, cancellationToken).ConfigureAwait(false);
try try
{ {
@@ -190,18 +188,21 @@ namespace MQTTnet.Adapter
chunkSize = bytesLeft; chunkSize = bytesLeft;
} }
#if WINDOWS_UWP
var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false);
#else
// async/await is not used to avoid the overhead of context switches. We assume that the reamining data // async/await is not used to avoid the overhead of context switches. We assume that the reamining data
// has been sent from the sender directly after the initial bytes. // has been sent from the sender directly after the initial bytes.
var readBytes = channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).GetAwaiter().GetResult(); var readBytes = _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult();
if (readBytes <= 0) #endif
{
ExceptionHelper.ThrowGracefulSocketClose(); cancellationToken.ThrowIfCancellationRequested();
} ExceptionHelper.ThrowIfGracefulSocketClose(readBytes);
bodyOffset += readBytes; bodyOffset += readBytes;
} while (bodyOffset < body.Length); } while (bodyOffset < body.Length);
return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0)); return new ReceivedMqttPacket(fixedHeader.Flags, new MqttPacketBodyReader(body, 0, body.Length));
} }
finally finally
{ {
@@ -7,5 +7,9 @@ namespace MQTTnet.Client
IMqttClient CreateMqttClient(); IMqttClient CreateMqttClient();
IMqttClient CreateMqttClient(IMqttNetLogger logger); IMqttClient CreateMqttClient(IMqttNetLogger logger);
IMqttClient CreateMqttClient(IMqttClientAdapterFactory adapterFactory);
IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory adapterFactory);
} }
} }
+25 -11
View File
@@ -25,10 +25,11 @@ namespace MQTTnet.Client
private IMqttClientOptions _options; private IMqttClientOptions _options;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private Task _packetReceiverTask; internal Task _packetReceiverTask;
private Task _keepAliveMessageSenderTask; internal Task _keepAliveMessageSenderTask;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
private bool _cleanDisconnectInitiated; private bool _cleanDisconnectInitiated;
private TaskCompletionSource<bool> _disconnectReason;
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{ {
@@ -54,6 +55,7 @@ namespace MQTTnet.Client
try try
{ {
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_disconnectReason = new TaskCompletionSource<bool>();
_options = options; _options = options;
_packetIdentifierProvider.Reset(); _packetIdentifierProvider.Reset();
_packetDispatcher.Reset(); _packetDispatcher.Reset();
@@ -85,8 +87,10 @@ namespace MQTTnet.Client
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error(exception, "Error while connecting with server."); _logger.Error(exception, "Error while connecting with server.");
await DisconnectInternalAsync(null, exception).ConfigureAwait(false); if (_disconnectReason.TrySetException(exception))
{
await DisconnectInternalAsync(null, exception).ConfigureAwait(false);
}
throw; throw;
} }
} }
@@ -104,7 +108,10 @@ namespace MQTTnet.Client
} }
finally finally
{ {
await DisconnectInternalAsync(null, null).ConfigureAwait(false); if (_disconnectReason.TrySetCanceled())
{
await DisconnectInternalAsync(null, null).ConfigureAwait(false);
}
} }
} }
@@ -176,10 +183,12 @@ namespace MQTTnet.Client
public void Dispose() public void Dispose()
{ {
_cancellationTokenSource?.Cancel (false);
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null; _cancellationTokenSource = null;
_adapter?.Dispose(); _adapter?.Dispose();
_adapter = null;
} }
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
@@ -238,10 +247,7 @@ namespace MQTTnet.Client
} }
finally finally
{ {
_adapter?.Dispose(); Dispose ();
_adapter = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_cleanDisconnectInitiated = false; _cleanDisconnectInitiated = false;
_logger.Info("Disconnected."); _logger.Info("Disconnected.");
@@ -352,7 +358,10 @@ namespace MQTTnet.Client
_logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets."); _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets.");
} }
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); if (_disconnectReason.TrySetException(exception))
{
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
}
} }
finally finally
{ {
@@ -384,6 +393,7 @@ namespace MQTTnet.Client
if (exception is OperationCanceledException) if (exception is OperationCanceledException)
{ {
_logger.Verbose ("MQTT OperationCanceled exception while receiving packets.");
} }
else if (exception is MqttCommunicationException) else if (exception is MqttCommunicationException)
{ {
@@ -395,7 +405,11 @@ namespace MQTTnet.Client
} }
_packetDispatcher.Dispatch(exception); _packetDispatcher.Dispatch(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
if (_disconnectReason.TrySetException(exception))
{
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
}
} }
finally finally
{ {
@@ -7,10 +7,11 @@ namespace MQTTnet.Client
public class MqttClientOptionsBuilder public class MqttClientOptionsBuilder
{ {
private readonly MqttClientOptions _options = new MqttClientOptions(); private readonly MqttClientOptions _options = new MqttClientOptions();
private MqttClientTcpOptions _tcpOptions; private MqttClientTcpOptions _tcpOptions;
private MqttClientWebSocketOptions _webSocketOptions; private MqttClientWebSocketOptions _webSocketOptions;
private MqttClientOptionsBuilderTlsParameters _tlsParameters;
private MqttClientTlsOptions _tlsOptions; private MqttClientWebSocketProxyOptions _proxyOptions;
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{ {
@@ -76,6 +77,21 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithProxy(string address, string username = null, string password = null, string domain = null, bool bypassOnLocal = false, string[] bypassList = null)
{
_proxyOptions = new MqttClientWebSocketProxyOptions
{
Address = address,
Username = username,
Password = password,
Domain = domain,
BypassOnLocal = bypassOnLocal,
BypassList = bypassList
};
return this;
}
public MqttClientOptionsBuilder WithWebSocketServer(string uri) public MqttClientOptionsBuilder WithWebSocketServer(string uri)
{ {
_webSocketOptions = new MqttClientWebSocketOptions _webSocketOptions = new MqttClientWebSocketOptions
@@ -86,13 +102,25 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
{
_tlsParameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
return this;
}
public MqttClientOptionsBuilder WithTls()
{
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
}
[Obsolete("Use method _WithTls_ which accepts the _MqttClientOptionsBuilderTlsParameters_.")]
public MqttClientOptionsBuilder WithTls( public MqttClientOptionsBuilder WithTls(
bool allowUntrustedCertificates = false, bool allowUntrustedCertificates = false,
bool ignoreCertificateChainErrors = false, bool ignoreCertificateChainErrors = false,
bool ignoreCertificateRevocationErrors = false, bool ignoreCertificateRevocationErrors = false,
params byte[][] certificates) params byte[][] certificates)
{ {
_tlsOptions = new MqttClientTlsOptions _tlsParameters = new MqttClientOptionsBuilderTlsParameters
{ {
UseTls = true, UseTls = true,
AllowUntrustedCertificates = allowUntrustedCertificates, AllowUntrustedCertificates = allowUntrustedCertificates,
@@ -104,33 +132,47 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithTls()
{
_tlsOptions = new MqttClientTlsOptions
{
UseTls = true
};
return this;
}
public IMqttClientOptions Build() public IMqttClientOptions Build()
{ {
if (_tlsOptions != null) if (_tcpOptions == null && _webSocketOptions == null)
{ {
if (_tcpOptions == null && _webSocketOptions == null) throw new InvalidOperationException("A channel must be set.");
}
if (_tlsParameters != null)
{
if (_tlsParameters?.UseTls == true)
{ {
throw new InvalidOperationException("A channel (TCP or WebSocket) must be set if TLS is configured."); var tlsOptions = new MqttClientTlsOptions
{
UseTls = true,
SslProtocol = _tlsParameters.SslProtocol,
AllowUntrustedCertificates = _tlsParameters.AllowUntrustedCertificates,
Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(),
CertificateValidationCallback = _tlsParameters.CertificateValidationCallback,
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors
};
if (_tcpOptions != null)
{
_tcpOptions.TlsOptions = tlsOptions;
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = tlsOptions;
}
}
}
if (_proxyOptions != null)
{
if (_webSocketOptions == null)
{
throw new InvalidOperationException("Proxies are only supported for WebSocket connections.");
} }
if (_tcpOptions != null) _webSocketOptions.ProxyOptions = _proxyOptions;
{
_tcpOptions.TlsOptions = _tlsOptions;
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = _tlsOptions;
}
} }
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions; _options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace MQTTnet.Client
{
public class MqttClientOptionsBuilderTlsParameters
{
public bool UseTls { get; set; }
public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback
{
get;
set;
}
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
public IEnumerable<IEnumerable<byte>> Certificates { get; set; }
public bool AllowUntrustedCertificates { get; set; }
public bool IgnoreCertificateChainErrors { get; set; }
public bool IgnoreCertificateRevocationErrors { get; set; }
}
}
@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Security; using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
@@ -17,6 +18,8 @@ namespace MQTTnet.Client
public List<byte[]> Certificates { get; set; } public List<byte[]> Certificates { get; set; }
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback { get; set; } public Func<X509Certificate, X509Chain, SslPolicyErrors, IMqttClientOptions, bool> CertificateValidationCallback { get; set; }
} }
} }
@@ -13,6 +13,8 @@ namespace MQTTnet.Client
public CookieContainer CookieContainer { get; set; } public CookieContainer CookieContainer { get; set; }
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
public override string ToString() public override string ToString()
@@ -0,0 +1,17 @@
namespace MQTTnet.Client
{
public class MqttClientWebSocketProxyOptions
{
public string Address { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
public bool BypassOnLocal { get; set; }
public string[] BypassList { get; set; }
}
}
@@ -154,7 +154,11 @@ namespace MQTTnet.Implementations
private void CreateStreams() private void CreateStreams()
{ {
_readStream = _socket.InputStream.AsStreamForRead(_bufferSize); // Attention! Do not set the buffer for the read method. This will
// limit the internal buffer and the read operation will hang forever
// if more data than the buffer size was received.
_readStream = _socket.InputStream.AsStreamForRead();
_writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); _writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
} }
@@ -63,7 +63,7 @@ namespace MQTTnet.Implementations
if (_options.TlsOptions.UseTls) if (_options.TlsOptions.UseTls)
{ {
sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), _options.TlsOptions.SslProtocol, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
} }
CreateStream(sslStream); CreateStream(sslStream);
@@ -87,8 +87,14 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
TryDispose(_stream, () => _stream = null); Cleanup(ref _stream, (s) => s.Dispose());
TryDispose(_socket, () => _socket = null); Cleanup(ref _socket, (s) => {
if (s.Connected)
{
s.Shutdown(SocketShutdown.Both);
}
s.Dispose();
});
} }
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
@@ -157,11 +163,16 @@ namespace MQTTnet.Implementations
} }
} }
private static void TryDispose(IDisposable disposable, Action afterDispose) private static void Cleanup<T>(ref T item, Action<T> handler) where T : class
{ {
var temp = item;
item = null;
try try
{ {
disposable?.Dispose(); if (temp != null)
{
handler(temp);
}
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
@@ -169,10 +180,6 @@ namespace MQTTnet.Implementations
catch (NullReferenceException) catch (NullReferenceException)
{ {
} }
finally
{
afterDispose();
}
} }
} }
} }
@@ -1,6 +1,7 @@
#if !WINDOWS_UWP #if !WINDOWS_UWP
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
@@ -79,27 +80,33 @@ namespace MQTTnet.Implementations
private void RegisterListeners(MqttServerTcpEndpointBaseOptions options, X509Certificate2 tlsCertificate) private void RegisterListeners(MqttServerTcpEndpointBaseOptions options, X509Certificate2 tlsCertificate)
{ {
var listenerV4 = new MqttTcpServerListener( if (!options.BoundInterNetworkAddress.Equals(IPAddress.None))
AddressFamily.InterNetwork, {
options, var listenerV4 = new MqttTcpServerListener(
tlsCertificate, AddressFamily.InterNetwork,
_cancellationTokenSource.Token, options,
_logger); tlsCertificate,
_cancellationTokenSource.Token,
_logger);
listenerV4.ClientAccepted += OnClientAccepted; listenerV4.ClientAccepted += OnClientAccepted;
listenerV4.Start(); listenerV4.Start();
_listeners.Add(listenerV4); _listeners.Add(listenerV4);
}
var listenerV6 = new MqttTcpServerListener( if (!options.BoundInterNetworkV6Address.Equals(IPAddress.None))
AddressFamily.InterNetworkV6, {
options, var listenerV6 = new MqttTcpServerListener(
tlsCertificate, AddressFamily.InterNetworkV6,
_cancellationTokenSource.Token, options,
_logger); tlsCertificate,
_cancellationTokenSource.Token,
_logger);
listenerV6.ClientAccepted += OnClientAccepted; listenerV6.ClientAccepted += OnClientAccepted;
listenerV6.Start(); listenerV6.Start();
_listeners.Add(listenerV6); _listeners.Add(listenerV6);
}
} }
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs e) private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs e)
@@ -78,8 +78,10 @@ namespace MQTTnet.Implementations
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
} }
var protocol = _addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6"; _logger.Verbose("Client '{0}' accepted by TCP listener '{1}, {2}'.",
_logger.Verbose($"Client '{clientSocket.RemoteEndPoint}' accepted by TCP listener '{_socket.LocalEndPoint}, {protocol}'."); clientSocket.RemoteEndPoint,
_socket.LocalEndPoint,
_addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6");
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
@@ -1,4 +1,5 @@
using System; using System;
using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
@@ -14,7 +15,7 @@ namespace MQTTnet.Implementations
private readonly MqttClientWebSocketOptions _options; private readonly MqttClientWebSocketOptions _options;
private WebSocket _webSocket; private WebSocket _webSocket;
public MqttWebSocketChannel(MqttClientWebSocketOptions options) public MqttWebSocketChannel(MqttClientWebSocketOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
@@ -45,6 +46,11 @@ namespace MQTTnet.Implementations
var clientWebSocket = new ClientWebSocket(); var clientWebSocket = new ClientWebSocket();
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
}
if (_options.RequestHeaders != null) if (_options.RequestHeaders != null)
{ {
foreach (var requestHeader in _options.RequestHeaders) foreach (var requestHeader in _options.RequestHeaders)
@@ -132,5 +138,31 @@ namespace MQTTnet.Implementations
_webSocket = null; _webSocket = null;
} }
} }
private IWebProxy CreateProxy()
{
if (string.IsNullOrEmpty(_options.ProxyOptions?.Address))
{
return null;
}
#if WINDOWS_UWP
throw new NotSupportedException("Proxies are not supported in UWP.");
#elif NETSTANDARD1_3
throw new NotSupportedException("Proxies are not supported in netstandard 1.3.");
#else
var proxyUri = new Uri(_options.ProxyOptions.Address);
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
{
var credentials =
new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
}
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
#endif
}
} }
} }
@@ -8,5 +8,13 @@ namespace MQTTnet.Internal
{ {
throw new MqttCommunicationClosedGracefullyException(); throw new MqttCommunicationClosedGracefullyException();
} }
public static void ThrowIfGracefulSocketClose(int readBytesCount)
{
if (readBytesCount <= 0)
{
throw new MqttCommunicationClosedGracefullyException();
}
}
} }
} }
+5 -5
View File
@@ -14,11 +14,7 @@ namespace MQTTnet.Internal
_stream = stream; _stream = stream;
} }
public void Dispose() public string Endpoint { get; } = "<Test channel>";
{
}
public string Endpoint { get; }
public Task ConnectAsync(CancellationToken cancellationToken) public Task ConnectAsync(CancellationToken cancellationToken)
{ {
@@ -39,5 +35,9 @@ namespace MQTTnet.Internal
{ {
return _stream.WriteAsync(buffer, offset, count, cancellationToken); return _stream.WriteAsync(buffer, offset, count, cancellationToken);
} }
public void Dispose()
{
}
} }
} }
+5 -4
View File
@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -20,7 +21,7 @@
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> <NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.17134.0</TargetPlatformVersion> <TargetPlatformVersion>10.0.17134.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.10586.212</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
@@ -53,7 +54,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'"> <ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.1.4" /> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.1.5" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net452'"> <ItemGroup Condition="'$(TargetFramework)'=='net452'">
+10 -3
View File
@@ -22,12 +22,19 @@ namespace MQTTnet
return new MqttClient(new MqttClientAdapterFactory(), logger); return new MqttClient(new MqttClientAdapterFactory(), logger);
} }
public IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory mqttClientAdapterFactory) public IMqttClient CreateMqttClient(IMqttClientAdapterFactory adapterFactory)
{
if (adapterFactory == null) throw new ArgumentNullException(nameof(adapterFactory));
return new MqttClient(adapterFactory, new MqttNetLogger());
}
public IMqttClient CreateMqttClient(IMqttNetLogger logger, IMqttClientAdapterFactory adapterFactory)
{ {
if (logger == null) throw new ArgumentNullException(nameof(logger)); if (logger == null) throw new ArgumentNullException(nameof(logger));
if (mqttClientAdapterFactory == null) throw new ArgumentNullException(nameof(mqttClientAdapterFactory)); if (adapterFactory == null) throw new ArgumentNullException(nameof(adapterFactory));
return new MqttClient(mqttClientAdapterFactory, logger); return new MqttClient(adapterFactory, logger);
} }
public IMqttServer CreateMqttServer() public IMqttServer CreateMqttServer()
@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
#if DEBUG
[assembly:InternalsVisibleTo("MQTTnet.Core.Tests")]
#endif
@@ -6,17 +6,20 @@ namespace MQTTnet.Serializer
public class MqttPacketBodyReader public class MqttPacketBodyReader
{ {
private readonly byte[] _buffer; private readonly byte[] _buffer;
private int _offset; private readonly int _length;
public MqttPacketBodyReader(byte[] buffer, int offset) private int _offset;
public MqttPacketBodyReader(byte[] buffer, int offset, int length)
{ {
_buffer = buffer; _buffer = buffer;
_offset = offset; _offset = offset;
_length = length;
} }
public int Length => _buffer.Length - _offset; public int Length => _length - _offset;
public bool EndOfStream => _offset == _buffer.Length; public bool EndOfStream => _offset == _length;
public byte ReadByte() public byte ReadByte()
{ {
@@ -26,7 +29,7 @@ namespace MQTTnet.Serializer
public ArraySegment<byte> ReadRemainingData() public ArraySegment<byte> ReadRemainingData()
{ {
return new ArraySegment<byte>(_buffer, _offset, _buffer.Length - _offset); return new ArraySegment<byte>(_buffer, _offset, _length - _offset);
} }
public ushort ReadUInt16() public ushort ReadUInt16()
@@ -53,9 +56,9 @@ namespace MQTTnet.Serializer
private void ValidateReceiveBuffer(ushort length) private void ValidateReceiveBuffer(ushort length)
{ {
if (_buffer.Length < _offset + length) if (_length < _offset + length)
{ {
throw new ArgumentOutOfRangeException(nameof(_buffer), $"expected at least {_offset + length} bytes but there are only {_buffer.Length} bytes"); throw new ArgumentOutOfRangeException(nameof(_buffer), $"expected at least {_offset + length} bytes but there are only {_length} bytes");
} }
} }
+72 -21
View File
@@ -1,5 +1,4 @@
using System; using System.Threading;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -21,11 +20,9 @@ namespace MQTTnet.Serializer
while (totalBytesRead < buffer.Length) while (totalBytesRead < buffer.Length)
{ {
var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false); var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
if (bytesRead <= 0)
{ cancellationToken.ThrowIfCancellationRequested();
cancellationToken.ThrowIfCancellationRequested(); ExceptionHelper.ThrowIfGracefulSocketClose(bytesRead);
ExceptionHelper.ThrowGracefulSocketClose();
}
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
@@ -35,34 +32,42 @@ namespace MQTTnet.Serializer
{ {
return new MqttFixedHeader(buffer[0], 0); return new MqttFixedHeader(buffer[0], 0);
} }
#if WINDOWS_UWP
// UWP will have a dead lock when calling this not async.
var bodyLength = await ReadBodyLengthAsync(channel, buffer[1], singleByteBuffer, cancellationToken).ConfigureAwait(false);
#else
// Here the async/await pattern is not used becuase the overhead of context switches
// is too big for reading 1 byte in a row. We expect that the remaining data was sent
// directly after the initial bytes. If the client disconnects just in this moment we
// will get an exception anyway.
var bodyLength = ReadBodyLength(channel, buffer[1], singleByteBuffer, cancellationToken); var bodyLength = ReadBodyLength(channel, buffer[1], singleByteBuffer, cancellationToken);
#endif
return new MqttFixedHeader(buffer[0], bodyLength); return new MqttFixedHeader(buffer[0], bodyLength);
} }
#if !WINDOWS_UWP
private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken) private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken)
{ {
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html. var offset = 0;
var multiplier = 128; var multiplier = 128;
var value = initialEncodedByte & 127; var value = initialEncodedByte & 127;
int encodedByte = initialEncodedByte; int encodedByte = initialEncodedByte;
while ((encodedByte & 128) != 0) while ((encodedByte & 128) != 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); offset++;
if (offset > 3)
// Here the async/await pattern is not used becuase the overhead of context switches
// is too big for reading 1 byte in a row. We expect that the remaining data was sent
// directly after the initial bytes. If the client disconnects just in this moment we
// will get an exception anyway.
encodedByte = ReadByte(channel, singleByteBuffer, cancellationToken);
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{ {
throw new MqttProtocolViolationException("Remaining length is invalid."); throw new MqttProtocolViolationException("Remaining length is invalid.");
} }
cancellationToken.ThrowIfCancellationRequested();
encodedByte = ReadByte(channel, singleByteBuffer, cancellationToken);
value += (byte)(encodedByte & 127) * multiplier;
multiplier *= 128; multiplier *= 128;
} }
@@ -71,14 +76,60 @@ namespace MQTTnet.Serializer
private static byte ReadByte(IMqttChannel channel, byte[] singleByteBuffer, CancellationToken cancellationToken) private static byte ReadByte(IMqttChannel channel, byte[] singleByteBuffer, CancellationToken cancellationToken)
{ {
var readCount = channel.ReadAsync(singleByteBuffer, 0, 1, cancellationToken).GetAwaiter().GetResult(); var readCount = channel.ReadAsync(singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult();
cancellationToken.ThrowIfCancellationRequested();
if (readCount <= 0) if (readCount <= 0)
{ {
cancellationToken.ThrowIfCancellationRequested();
ExceptionHelper.ThrowGracefulSocketClose(); ExceptionHelper.ThrowGracefulSocketClose();
} }
return singleByteBuffer[0]; return singleByteBuffer[0];
} }
#else
private static async Task<int> ReadBodyLengthAsync(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken)
{
var offset = 0;
var multiplier = 128;
var value = initialEncodedByte & 127;
int encodedByte = initialEncodedByte;
while ((encodedByte & 128) != 0)
{
offset++;
if (offset > 3)
{
throw new MqttProtocolViolationException("Remaining length is invalid.");
}
cancellationToken.ThrowIfCancellationRequested();
encodedByte = await ReadByteAsync(channel, singleByteBuffer, cancellationToken).ConfigureAwait(false);
value += (byte)(encodedByte & 127) * multiplier;
multiplier *= 128;
}
return value;
}
private static async Task<byte> ReadByteAsync(IMqttChannel channel, byte[] singleByteBuffer, CancellationToken cancellationToken)
{
var readCount = await channel.ReadAsync(singleByteBuffer, 0, 1, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
if (readCount <= 0)
{
ExceptionHelper.ThrowGracefulSocketClose();
}
return singleByteBuffer[0];
}
#endif
} }
} }
@@ -29,7 +29,6 @@ namespace MQTTnet.Serializer
public static ArraySegment<byte> EncodeRemainingLength(int length) public static ArraySegment<byte> EncodeRemainingLength(int length)
{ {
// write the encoded remaining length right aligned on the 4 byte buffer
if (length <= 0) if (length <= 0)
{ {
return new ArraySegment<byte>(new byte[1], 0, 1); return new ArraySegment<byte>(new byte[1], 0, 1);
@@ -38,7 +37,6 @@ namespace MQTTnet.Serializer
var buffer = new byte[4]; var buffer = new byte[4];
var bufferOffset = 0; var bufferOffset = 0;
// Algorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = length; var x = length;
do do
{ {
+1 -1
View File
@@ -14,7 +14,7 @@ namespace MQTTnet.Server
void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket); void EnqueueApplicationMessage(MqttClientSession senderClientSession, MqttPublishPacket publishPacket);
void ClearPendingApplicationMessages(); void ClearPendingApplicationMessages();
Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter); Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter);
void Stop(MqttClientDisconnectType disconnectType); void Stop(MqttClientDisconnectType disconnectType);
Task SubscribeAsync(IList<TopicFilter> topicFilters); Task SubscribeAsync(IList<TopicFilter> topicFilters);
+2
View File
@@ -23,5 +23,7 @@ namespace MQTTnet.Server
Task StartAsync(IMqttServerOptions options); Task StartAsync(IMqttServerOptions options);
Task StopAsync(); Task StopAsync();
Task ClearRetainedMessagesAsync();
} }
} }
@@ -8,6 +8,8 @@ namespace MQTTnet.Server
{ {
IMqttServer CreateMqttServer(); IMqttServer CreateMqttServer();
IMqttServer CreateMqttServer(IMqttNetLogger logger);
IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger); IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger);
} }
} }
@@ -14,9 +14,9 @@ namespace MQTTnet.Server
private readonly IMqttClientSession _clientSession; private readonly IMqttClientSession _clientSession;
private readonly IMqttNetChildLogger _logger; private readonly IMqttNetChildLogger _logger;
private bool _isPaused; private bool _isPaused;
public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger) public MqttClientKeepAliveMonitor(IMqttClientSession clientSession, IMqttNetChildLogger logger)
{ {
if (logger == null) throw new ArgumentNullException(nameof(logger)); if (logger == null) throw new ArgumentNullException(nameof(logger));
@@ -70,15 +70,21 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
// Values described here: [MQTT-3.1.2-24]. // Values described here: [MQTT-3.1.2-24].
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D) // If the client sends 5 sec. the server will allow up to 7.5 seconds.
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
if (!_isPaused && _lastPacketReceivedTracker.Elapsed.TotalSeconds >= keepAlivePeriod * 1.5D)
{ {
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId); _logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", _clientSession.ClientId);
_clientSession.Stop(MqttClientDisconnectType.NotClean); _clientSession.Stop(MqttClientDisconnectType.NotClean);
return; return;
} }
await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false); // The server checks the keep alive timeout every 50 % of the overall keep alive timeout
// because the server allows 1.5 times the keep alive value. This means that a value of 5 allows
// up to 7.5 seconds. With an interval of 2.5 (5 / 2) the 7.5 is also affected. Waiting the whole
// keep alive time will hit at 10 instead of 7.5 (but only one time instead of two times).
await Task.Delay(TimeSpan.FromSeconds(keepAlivePeriod * 0.5D), cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -90,7 +96,7 @@ namespace MQTTnet.Server
} }
finally finally
{ {
_logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientSession.ClientId); _logger.Verbose("Client '{0}': Stopped checking keep alive timeout.", _clientSession.ClientId);
} }
} }
} }
+69 -15
View File
@@ -29,6 +29,8 @@ namespace MQTTnet.Server
private MqttApplicationMessage _willMessage; private MqttApplicationMessage _willMessage;
private bool _wasCleanDisconnect; private bool _wasCleanDisconnect;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
private Task _workerTask;
private IDisposable _cleanupHandle;
public MqttClientSession( public MqttClientSession(
string clientId, string clientId,
@@ -65,7 +67,13 @@ namespace MQTTnet.Server
status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived; status.LastNonKeepAlivePacketReceived = _keepAliveMonitor.LastNonKeepAlivePacketReceived;
} }
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{
_workerTask = RunInternalAsync(connectPacket, adapter);
return _workerTask;
}
private async Task RunInternalAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{ {
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); if (adapter == null) throw new ArgumentNullException(nameof(adapter));
@@ -73,10 +81,18 @@ namespace MQTTnet.Server
try try
{ {
_adapter = adapter; _adapter = adapter;
adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted; adapter.ReadingPacketStarted += OnAdapterReadingPacketStarted;
adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted; adapter.ReadingPacketCompleted += OnAdapterReadingPacketCompleted;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
//woraround for https://github.com/dotnet/corefx/issues/24430
#pragma warning disable 4014
_cleanupHandle = _cancellationTokenSource.Token.Register(() => CleanupAsync());
#pragma warning restore 4014
//endworkaround
_wasCleanDisconnect = false; _wasCleanDisconnect = false;
_willMessage = connectPacket.WillMessage; _willMessage = connectPacket.WillMessage;
@@ -114,26 +130,55 @@ namespace MQTTnet.Server
_logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId); _logger.Error(exception, "Client '{0}': Unhandled exception while receiving client packets.", ClientId);
} }
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
} }
finally finally
{ {
if (_adapter != null) await CleanupAsync().ConfigureAwait(false);
_cleanupHandle?.Dispose();
_cleanupHandle = null;
_adapter = null;
_cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
}
private async Task CleanupAsync()
{
var adapter = _adapter;
try
{
if (adapter == null)
{ {
_adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted; return;
_adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
} }
_adapter = null; _adapter = null;
_cancellationTokenSource?.Dispose(); adapter.ReadingPacketStarted -= OnAdapterReadingPacketStarted;
_cancellationTokenSource = null; adapter.ReadingPacketCompleted -= OnAdapterReadingPacketCompleted;
}
return _wasCleanDisconnect; await adapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
finally
{
adapter?.Dispose();
}
} }
public void Stop(MqttClientDisconnectType type) public void Stop(MqttClientDisconnectType type)
{
Stop(type, false);
}
private void Stop(MqttClientDisconnectType type, bool isInsideSession)
{ {
try try
{ {
@@ -153,10 +198,17 @@ namespace MQTTnet.Server
} }
_willMessage = null; _willMessage = null;
if (!isInsideSession)
{
_workerTask?.GetAwaiter().GetResult();
}
} }
finally finally
{ {
_logger.Info("Client '{0}': Session stopped.", ClientId); _logger.Info("Client '{0}': Session stopped.", ClientId);
_sessionsManager.Server.OnClientDisconnected(ClientId, _wasCleanDisconnect);
} }
} }
@@ -240,7 +292,9 @@ namespace MQTTnet.Server
{ {
_pendingPacketsQueue?.Dispose(); _pendingPacketsQueue?.Dispose();
_cancellationTokenSource?.Cancel ();
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
} }
private void ProcessReceivedPacket(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken) private void ProcessReceivedPacket(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
@@ -298,18 +352,18 @@ namespace MQTTnet.Server
if (packet is MqttDisconnectPacket) if (packet is MqttDisconnectPacket)
{ {
Stop(MqttClientDisconnectType.Clean); Stop(MqttClientDisconnectType.Clean, true);
return; return;
} }
if (packet is MqttConnectPacket) if (packet is MqttConnectPacket)
{ {
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
return; return;
} }
_logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); _logger.Warning(null, "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
} }
private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters) private void EnqueueSubscribedRetainedMessages(ICollection<TopicFilter> topicFilters)
@@ -328,7 +382,7 @@ namespace MQTTnet.Server
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
{ {
Stop(MqttClientDisconnectType.NotClean); Stop(MqttClientDisconnectType.NotClean, true);
return; return;
} }
@@ -405,12 +459,12 @@ namespace MQTTnet.Server
private void OnAdapterReadingPacketCompleted(object sender, EventArgs e) private void OnAdapterReadingPacketCompleted(object sender, EventArgs e)
{ {
_keepAliveMonitor?.Pause(); _keepAliveMonitor?.Resume();
} }
private void OnAdapterReadingPacketStarted(object sender, EventArgs e) private void OnAdapterReadingPacketStarted(object sender, EventArgs e)
{ {
_keepAliveMonitor?.Resume(); _keepAliveMonitor?.Pause();
} }
} }
} }
@@ -44,7 +44,7 @@ namespace MQTTnet.Server
public void Start() public void Start()
{ {
Task.Factory.StartNew(() => ProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); Task.Factory.StartNew(() => TryProcessQueuedApplicationMessages(_cancellationToken), _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} }
public void Stop() public void Stop()
@@ -125,6 +125,7 @@ namespace MQTTnet.Server
{ {
_sessions.Remove(clientId); _sessions.Remove(clientId);
} }
_logger.Verbose("Session for client '{0}' deleted.", clientId); _logger.Verbose("Session for client '{0}' deleted.", clientId);
} }
@@ -133,54 +134,69 @@ namespace MQTTnet.Server
_messageQueue?.Dispose(); _messageQueue?.Dispose();
} }
private void ProcessQueuedApplicationMessages(CancellationToken cancellationToken) private void TryProcessQueuedApplicationMessages(CancellationToken cancellationToken)
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
try try
{ {
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken); TryProcessNextQueuedApplicationMessage(cancellationToken);
var sender = enqueuedApplicationMessage.Sender;
var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage();
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage);
if (interceptorContext != null)
{
if (interceptorContext.CloseConnection)
{
enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
applicationMessage = interceptorContext.ApplicationMessage;
}
Server.OnApplicationMessageReceived(sender?.ClientId, applicationMessage);
if (applicationMessage.Retain)
{
_retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult();
}
foreach (var clientSession in GetSessions())
{
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket());
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error(exception, "Unhandled exception while processing queued application message."); _logger.Error(exception, "Unhandled exception while processing queued application messages.");
} }
} }
} }
private void TryProcessNextQueuedApplicationMessage(CancellationToken cancellationToken)
{
try
{
var enqueuedApplicationMessage = _messageQueue.Take(cancellationToken);
var sender = enqueuedApplicationMessage.Sender;
var applicationMessage = enqueuedApplicationMessage.PublishPacket.ToApplicationMessage();
var interceptorContext = InterceptApplicationMessage(sender, applicationMessage);
if (interceptorContext != null)
{
if (interceptorContext.CloseConnection)
{
enqueuedApplicationMessage.Sender.Stop(MqttClientDisconnectType.NotClean);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
applicationMessage = interceptorContext.ApplicationMessage;
}
Server.OnApplicationMessageReceived(sender?.ClientId, applicationMessage);
if (applicationMessage.Retain)
{
_retainedMessagesManager.HandleMessageAsync(sender?.ClientId, applicationMessage).GetAwaiter().GetResult();
}
foreach (var clientSession in GetSessions())
{
clientSession.EnqueueApplicationMessage(enqueuedApplicationMessage.Sender, applicationMessage.ToPublishPacket());
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Unhandled exception while processing next queued application message.");
}
}
private List<MqttClientSession> GetSessions() private List<MqttClientSession> GetSessions()
{ {
lock (_sessions) lock (_sessions)
@@ -192,8 +208,7 @@ namespace MQTTnet.Server
private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) private async Task RunSession(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
{ {
var clientId = string.Empty; var clientId = string.Empty;
var wasCleanDisconnect = false;
try try
{ {
var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false); var firstPacket = await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false);
@@ -212,7 +227,7 @@ namespace MQTTnet.Server
// Switch to the required protocol version before sending any response. // Switch to the required protocol version before sending any response.
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion; clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
var connectReturnCode = ValidateConnection(connectPacket); var connectReturnCode = ValidateConnection(connectPacket, clientAdapter);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{ {
await clientAdapter.SendPacketAsync( await clientAdapter.SendPacketAsync(
@@ -238,7 +253,7 @@ namespace MQTTnet.Server
Server.OnClientConnected(clientId); Server.OnClientConnected(clientId);
wasCleanDisconnect = await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -249,26 +264,14 @@ namespace MQTTnet.Server
} }
finally finally
{ {
try
{
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
clientAdapter.Dispose();
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
}
if (!_options.EnablePersistentSessions) if (!_options.EnablePersistentSessions)
{ {
DeleteSession(clientId); DeleteSession(clientId);
} }
Server.OnClientDisconnected(clientId, wasCleanDisconnect);
} }
} }
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{ {
if (_options.ConnectionValidator == null) if (_options.ConnectionValidator == null)
{ {
@@ -279,7 +282,8 @@ namespace MQTTnet.Server
connectPacket.ClientId, connectPacket.ClientId,
connectPacket.Username, connectPacket.Username,
connectPacket.Password, connectPacket.Password,
connectPacket.WillMessage); connectPacket.WillMessage,
clientAdapter.Endpoint);
_options.ConnectionValidator(context); _options.ConnectionValidator(context);
return context.ReturnCode; return context.ReturnCode;
@@ -4,12 +4,13 @@ namespace MQTTnet.Server
{ {
public class MqttConnectionValidatorContext public class MqttConnectionValidatorContext
{ {
public MqttConnectionValidatorContext(string clientId, string username, string password, MqttApplicationMessage willMessage) public MqttConnectionValidatorContext(string clientId, string username, string password, MqttApplicationMessage willMessage, string endpoint)
{ {
ClientId = clientId; ClientId = clientId;
Username = username; Username = username;
Password = password; Password = password;
WillMessage = willMessage; WillMessage = willMessage;
Endpoint = endpoint;
} }
public string ClientId { get; } public string ClientId { get; }
@@ -20,6 +21,8 @@ namespace MQTTnet.Server
public MqttApplicationMessage WillMessage { get; } public MqttApplicationMessage WillMessage { get; }
public string Endpoint { get; }
public MqttConnectReturnCode ReturnCode { get; set; } = MqttConnectReturnCode.ConnectionAccepted; public MqttConnectReturnCode ReturnCode { get; set; } = MqttConnectReturnCode.ConnectionAccepted;
} }
} }
@@ -84,6 +84,16 @@ namespace MQTTnet.Server
return retainedMessages; return retainedMessages;
} }
public Task ClearMessagesAsync()
{
lock (_messages)
{
_messages.Clear();
}
return _options.Storage.SaveRetainedMessagesAsync(new List<MqttApplicationMessage>());
}
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
{ {
var saveIsRequired = false; var saveIsRequired = false;
@@ -93,7 +103,7 @@ namespace MQTTnet.Server
if (applicationMessage.Payload?.Length == 0) if (applicationMessage.Payload?.Length == 0)
{ {
saveIsRequired = _messages.Remove(applicationMessage.Topic); saveIsRequired = _messages.Remove(applicationMessage.Topic);
_logger.Info("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic); _logger.Verbose("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
} }
else else
{ {
@@ -111,7 +121,7 @@ namespace MQTTnet.Server
} }
} }
_logger.Info("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic); _logger.Verbose("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
} }
} }
+7 -2
View File
@@ -106,14 +106,14 @@ namespace MQTTnet.Server
_cancellationTokenSource.Cancel(false); _cancellationTokenSource.Cancel(false);
_clientSessionsManager.Stop();
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
adapter.ClientAccepted -= OnClientAccepted; adapter.ClientAccepted -= OnClientAccepted;
await adapter.StopAsync().ConfigureAwait(false); await adapter.StopAsync().ConfigureAwait(false);
} }
_clientSessionsManager.Stop();
_logger.Info("Stopped."); _logger.Info("Stopped.");
Stopped?.Invoke(this, EventArgs.Empty); Stopped?.Invoke(this, EventArgs.Empty);
} }
@@ -129,6 +129,11 @@ namespace MQTTnet.Server
} }
} }
public Task ClearRetainedMessagesAsync()
{
return _retainedMessagesManager?.ClearMessagesAsync();
}
internal void OnClientConnected(string clientId) internal void OnClientConnected(string clientId)
{ {
_logger.Info("Client '{0}': Connected.", clientId); _logger.Info("Client '{0}': Connected.", clientId);
@@ -58,7 +58,7 @@ namespace MQTTnet.Benchmarks
var receivedPacket = new ReceivedMqttPacket( var receivedPacket = new ReceivedMqttPacket(
header.Flags, header.Flags,
new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength)); new MqttPacketBodyReader(_serializedPacket.Array, _serializedPacket.Count - header.RemainingLength, _serializedPacket.Array.Length));
_serializer.Deserialize(receivedPacket); _serializer.Deserialize(receivedPacket);
} }
@@ -61,25 +61,26 @@ namespace MQTTnet.Core.Tests
} }
} }
/// <summary> // This test does not work in appveyor but on local machine it does!?
/// Verifies that inlining continuations do not have to complete execution before Set() returns. /////// <summary>
/// </summary> /////// Verifies that inlining continuations do not have to complete execution before Set() returns.
[TestMethod] /////// </summary>
public async Task SetReturnsBeforeInlinedContinuations() ////[TestMethod]
{ ////public async Task SetReturnsBeforeInlinedContinuations()
var setReturned = new ManualResetEventSlim(); ////{
var inlinedContinuation = _aare.WaitOneAsync() //// var setReturned = new ManualResetEventSlim();
.ContinueWith(delegate //// var inlinedContinuation = _aare.WaitOneAsync()
{ //// .ContinueWith(delegate
// Arrange to synchronously block the continuation until Set() has returned, //// {
// which would deadlock if Set does not return until inlined continuations complete. //// // Arrange to synchronously block the continuation until Set() has returned,
Assert.IsTrue(setReturned.Wait(500)); //// // which would deadlock if Set does not return until inlined continuations complete.
}); //// Assert.IsTrue(setReturned.Wait(500));
await Task.Delay(100); //// });
_aare.Set(); //// await Task.Delay(100);
setReturned.Set(); //// _aare.Set();
Assert.IsTrue(inlinedContinuation.Wait(500)); //// setReturned.Set();
} //// Assert.IsTrue(inlinedContinuation.Wait(500));
////}
[TestMethod] [TestMethod]
public void WaitAsync_WithCancellationToken() public void WaitAsync_WithCancellationToken()
+46 -6
View File
@@ -1,25 +1,29 @@
using System.Net.Sockets; using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Packets;
namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
{ {
[TestClass] [TestClass]
public class MqttClientTests public class MqttClientTests
{ {
[TestMethod] [TestMethod]
public async Task ClientDisconnectException() public async Task ClientDisconnectException()
{ {
var factory = new MqttFactory(); var factory = new MqttFactory();
var client = factory.CreateMqttClient(); var client = factory.CreateMqttClient();
var exceptionIsCorrect = false; Exception ex = null;
client.Disconnected += (s, e) => client.Disconnected += (s, e) =>
{ {
exceptionIsCorrect = e.Exception is MqttCommunicationException c && c.InnerException is SocketException; ex = e.Exception;
}; };
try try
@@ -29,8 +33,44 @@ namespace MQTTnet.Core.Tests
catch catch
{ {
} }
Assert.IsTrue(exceptionIsCorrect); Assert.IsNotNull(ex);
Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException));
Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException));
} }
#if DEBUG
[TestMethod]
public async Task ClientCleanupOnAuthentificationFails()
{
var channel = new TestMqttCommunicationAdapter();
var channel2 = new TestMqttCommunicationAdapter();
channel.Partner = channel2;
channel2.Partner = channel;
Task.Run(async () => {
var connect = await channel2.ReceivePacketAsync(TimeSpan.Zero, CancellationToken.None);
await channel2.SendPacketAsync(new MqttConnAckPacket() { ConnectReturnCode = Protocol.MqttConnectReturnCode.ConnectionRefusedNotAuthorized }, CancellationToken.None);
});
var fake = new TestMqttCommunicationAdapterFactory(channel);
var client = new MqttClient(fake, new MqttNetLogger());
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("any-server").Build());
}
catch (Exception ex)
{
Assert.IsInstanceOfType(ex, typeof(MqttConnectingFailedException));
}
Assert.IsTrue(client._packetReceiverTask == null || client._packetReceiverTask.IsCompleted, "receive loop not completed");
Assert.IsTrue(client._keepAliveMessageSenderTask == null || client._keepAliveMessageSenderTask.IsCompleted, "keepalive loop not completed");
}
#endif
} }
} }
@@ -60,7 +60,7 @@ namespace MQTTnet.Core.Tests
{ {
public string ClientId { get; } public string ClientId { get; }
public int StopCalledCount { get; set; } public int StopCalledCount { get; private set; }
public void FillStatus(MqttClientSessionStatus status) public void FillStatus(MqttClientSessionStatus status)
{ {
@@ -77,7 +77,7 @@ namespace MQTTnet.Core.Tests
throw new NotSupportedException(); throw new NotSupportedException();
} }
public Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -149,6 +150,53 @@ namespace MQTTnet.Core.Tests
DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310); DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310);
} }
[TestMethod]
public void Serialize_LargePacket()
{
var serializer = new MqttPacketSerializer { ProtocolVersion = MqttProtocolVersion.V311 };
const int payloadLength = 80000;
var payload = new byte[payloadLength];
var value = 0;
for (var i = 0; i < payloadLength; i++)
{
if (value > 255)
{
value = 0;
}
payload[i] = (byte)value;
}
var publishPacket = new MqttPublishPacket
{
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
Payload = payload
};
var buffer = serializer.Serialize(publishPacket);
var testChannel = new TestMqttChannel(new MemoryStream(buffer.Array, buffer.Offset, buffer.Count));
var header = MqttPacketReader.ReadFixedHeaderAsync(
testChannel,
new byte[2],
new byte[1],
CancellationToken.None).GetAwaiter().GetResult();
var eof = buffer.Offset + buffer.Count;
var receivedPacket = new ReceivedMqttPacket(
header.Flags,
new MqttPacketBodyReader(buffer.Array, eof - header.RemainingLength, buffer.Count + buffer.Offset));
var packet = (MqttPublishPacket)serializer.Deserialize(receivedPacket);
Assert.AreEqual(publishPacket.Topic, packet.Topic);
Assert.IsTrue(publishPacket.Payload.SequenceEqual(packet.Payload));
}
[TestMethod] [TestMethod]
public void SerializeV311_MqttDisconnectPacket() public void SerializeV311_MqttDisconnectPacket()
{ {
@@ -463,7 +511,7 @@ namespace MQTTnet.Core.Tests
{ {
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var data = serializer.Serialize(packet); var data = serializer.Serialize(packet);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data))); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data)));
} }
@@ -479,10 +527,10 @@ namespace MQTTnet.Core.Tests
var fixedHeader = new byte[2]; var fixedHeader = new byte[2];
var singleByteBuffer = new byte[1]; var singleByteBuffer = new byte[1];
var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult(); var header = MqttPacketReader.ReadFixedHeaderAsync(channel, fixedHeader, singleByteBuffer, CancellationToken.None).GetAwaiter().GetResult();
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength))
{ {
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0))); var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length)));
var buffer2 = serializer.Serialize(deserializedPacket); var buffer2 = serializer.Serialize(deserializedPacket);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2)));
@@ -507,7 +555,7 @@ namespace MQTTnet.Core.Tests
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength)) using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.RemainingLength))
{ {
return (T)serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0))); return (T)serializer.Deserialize(new ReceivedMqttPacket(header.Flags, new MqttPacketBodyReader(bodyStream.ToArray(), 0, (int)bodyStream.Length)));
} }
} }
} }
+120 -1
View File
@@ -233,6 +233,74 @@ namespace MQTTnet.Core.Tests
await c1.PublishAsync(message); await c1.PublishAsync(message);
} }
} }
[TestMethod]
public async Task MqttServer_ShutdownDisconnectsClientsGracefully()
{
var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger());
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build();
var disconnectCalled = 0;
await s.StartAsync(new MqttServerOptions());
var c1 = new MqttFactory().CreateMqttClient();
c1.Disconnected += (sender, args) => disconnectCalled++;
await c1.ConnectAsync(clientOptions);
await Task.Delay(100);
await s.StopAsync();
await Task.Delay(100);
Assert.AreEqual(1, disconnectCalled);
}
[TestMethod]
public async Task MqttServer_HandleCleanDisconnect()
{
MqttNetGlobalLogger.LogMessagePublished += (_, e) =>
{
System.Diagnostics.Debug.WriteLine($"[{e.TraceMessage.Timestamp:s}] {e.TraceMessage.Source} {e.TraceMessage.Message}");
};
var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger());
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var clientConnectedCalled = 0;
var clientDisconnectedCalled = 0;
s.ClientConnected += (_, __) => clientConnectedCalled++;
s.ClientDisconnected += (_, __) => clientDisconnectedCalled++;
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build();
await s.StartAsync(new MqttServerOptions());
var c1 = new MqttFactory().CreateMqttClient();
await c1.ConnectAsync(clientOptions);
await Task.Delay(100);
await c1.DisconnectAsync();
await Task.Delay(100);
await s.StopAsync();
await Task.Delay(100);
Assert.AreEqual(clientConnectedCalled, clientDisconnectedCalled);
}
[TestMethod] [TestMethod]
public async Task MqttServer_RetainedMessagesFlow() public async Task MqttServer_RetainedMessagesFlow()
@@ -375,6 +443,9 @@ namespace MQTTnet.Core.Tests
var c1 = await serverAdapter.ConnectTestClient("c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await Task.Delay(250);
await c1.DisconnectAsync(); await c1.DisconnectAsync();
} }
finally finally
@@ -396,7 +467,7 @@ namespace MQTTnet.Core.Tests
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build());
await Task.Delay(500); await Task.Delay(250);
} }
finally finally
{ {
@@ -481,6 +552,54 @@ namespace MQTTnet.Core.Tests
Assert.IsTrue(bodyIsMatching); Assert.IsTrue(bodyIsMatching);
} }
[TestMethod]
public async Task MqttServer_SameClientIdConnectDisconnectEventOrder()
{
var serverAdapter = new MqttTcpServerAdapter(new MqttNetLogger().CreateChildLogger());
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var connectedClient = false;
var connecteCalledBeforeConnectedClients = false;
s.ClientConnected += (_, __) =>
{
connecteCalledBeforeConnectedClients |= connectedClient;
connectedClient = true;
};
s.ClientDisconnected += (_, __) =>
{
connectedClient = false;
};
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.WithClientId(Guid.NewGuid().ToString())
.Build();
await s.StartAsync(new MqttServerOptions());
var c1 = new MqttFactory().CreateMqttClient();
var c2 = new MqttFactory().CreateMqttClient();
await c1.ConnectAsync(clientOptions);
await Task.Delay(100);
await c2.ConnectAsync(clientOptions);
await Task.Delay(100);
await c1.DisconnectAsync();
await c2.DisconnectAsync();
await s.StopAsync();
await Task.Delay(100);
Assert.IsFalse(connecteCalledBeforeConnectedClients, "ClientConnected was called before ClientDisconnect was called");
}
private class TestStorage : IMqttServerStorage private class TestStorage : IMqttServerStorage
{ {
public IList<MqttApplicationMessage> Messages = new List<MqttApplicationMessage>(); public IList<MqttApplicationMessage> Messages = new List<MqttApplicationMessage>();
+7 -2
View File
@@ -22,13 +22,18 @@ namespace MQTTnet.TestApp.AspNetCore2
.Build(); .Build();
services services
.AddHostedMqttServer(mqttServerOptions) .AddHostedMqttServer(mqttServerOptions)
.AddMqttConnectionHandler(); .AddMqttConnectionHandler()
.AddConnections();
} }
// In class _Startup_ of the ASP.NET Core 2.0 project. // In class _Startup_ of the ASP.NET Core 2.0 project.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ {
app.UseMqttEndpoint(); app.UseConnections(c => c.MapConnectionHandler<MqttConnectionHandler>("/mqtt", options => {
options.WebSockets.SubProtocolSelector = MQTTnet.AspNetCore.ApplicationBuilderExtensions.SelectSubProtocol;
}));
//app.UseMqttEndpoint();
app.UseMqttServer(server => app.UseMqttServer(server =>
{ {
server.Started += async (sender, args) => server.Started += async (sender, args) =>
@@ -3,6 +3,7 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -12,7 +13,7 @@ namespace MQTTnet.TestApp.NetCore
{ {
public static async Task RunAsync() public static async Task RunAsync()
{ {
//MqttNetGlobalLogger.LogMessagePublished += (s, e) => Console.WriteLine(e.TraceMessage); //MqttNetConsoleLogger.ForwardToConsole();
// iot.eclipse.org // iot.eclipse.org
await ExecuteTestAsync("iot.eclipse.org TCP", await ExecuteTestAsync("iot.eclipse.org TCP",
@@ -35,7 +36,7 @@ namespace MQTTnet.TestApp.NetCore
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").Build()); new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").Build());
await ExecuteTestAsync("test.mosquitto.org WS TLS", await ExecuteTestAsync("test.mosquitto.org WS TLS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").Build()); new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithTls().Build());
// broker.hivemq.com // broker.hivemq.com
await ExecuteTestAsync("broker.hivemq.com TCP", await ExecuteTestAsync("broker.hivemq.com TCP",
@@ -78,7 +79,7 @@ namespace MQTTnet.TestApp.NetCore
var topic = Guid.NewGuid().ToString(); var topic = Guid.NewGuid().ToString();
MqttApplicationMessage receivedMessage = null; MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceived += (s, e) => receivedMessage = e.ApplicationMessage; client.ApplicationMessageReceived += (s, e) => receivedMessage = e.ApplicationMessage;
await client.ConnectAsync(options); await client.ConnectAsync(options);
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce); await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
@@ -142,7 +142,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.1.4</Version> <Version>6.1.5</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls"> <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>3.0.0</Version> <Version>3.0.0</Version>
@@ -5,7 +5,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:server="using:MQTTnet.Server" xmlns:server="using:MQTTnet.Server"
xmlns:interop="using:Windows.UI.Xaml.Interop"
d:DesignHeight="800" d:DesignHeight="800"
d:DesignWidth="800" d:DesignWidth="800"
mc:Ignorable="d"> mc:Ignorable="d">
@@ -19,26 +18,55 @@
<Pivot Grid.Row="0"> <Pivot Grid.Row="0">
<PivotItem Header="Connection"> <PivotItem Header="Connection">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock>Server:</TextBlock> <Grid>
<TextBox x:Name="Server"></TextBox> <Grid.Resources>
<TextBlock>Port:</TextBlock> <Style TargetType="TextBlock">
<TextBox x:Name="Port" Text="1883"></TextBox> <Setter Property="VerticalAlignment" Value="Center"></Setter>
<TextBlock>User:</TextBlock> <Setter Property="Margin" Value="0, 0, 10, 0"></Setter>
<TextBox x:Name="User"></TextBox> </Style>
<TextBlock>Password:</TextBlock> </Grid.Resources>
<TextBox x:Name="Password"></TextBox> <Grid.ColumnDefinitions>
<TextBlock>ClientId:</TextBlock> <ColumnDefinition Width="Auto"></ColumnDefinition>
<TextBox x:Name="ClientId"></TextBox> <ColumnDefinition Width="*"></ColumnDefinition>
<TextBlock>Clean session:</TextBlock> </Grid.ColumnDefinitions>
<CheckBox x:Name="CleanSession" IsChecked="True"></CheckBox> <Grid.RowDefinitions>
<TextBlock>Keep alive interval:</TextBlock> <RowDefinition></RowDefinition>
<TextBox x:Name="KeepAliveInterval" Text="5"></TextBox> <RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"> <TextBlock Grid.Column="0" Grid.Row="0">Server:</TextBlock>
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton> <TextBox Grid.Column="1" Grid.Row="0" x:Name="Server" Text="localhost"></TextBox>
<RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton>
<CheckBox x:Name="UseTls">Use TLS</CheckBox> <TextBlock Grid.Column="0" Grid.Row="1">Port:</TextBlock>
</StackPanel> <TextBox Grid.Column="1" Grid.Row="1" x:Name="Port" Text="1883"></TextBox>
<TextBlock Grid.Column="0" Grid.Row="2">Username:</TextBlock>
<TextBox Grid.Column="1" Grid.Row="2" x:Name="User"></TextBox>
<TextBlock Grid.Column="0" Grid.Row="3">Password:</TextBlock>
<TextBox Grid.Column="1" Grid.Row="3" x:Name="Password"></TextBox>
<TextBlock Grid.Column="0" Grid.Row="4">Client ID:</TextBlock>
<TextBox Grid.Column="1" Grid.Row="4" x:Name="ClientId"></TextBox>
<TextBlock Grid.Column="0" Grid.Row="5">Keep Alive interval:</TextBlock>
<TextBox Grid.Column="1" Grid.Row="5" x:Name="KeepAliveInterval" Text="5"></TextBox>
<TextBlock Grid.Column="0" Grid.Row="6">Protocol:</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="6" Orientation="Horizontal">
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton>
<RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton>
<CheckBox x:Name="UseTls">Use TLS</CheckBox>
</StackPanel>
</Grid>
<CheckBox x:Name="CleanSession" IsChecked="True">Clean session</CheckBox>
<CheckBox x:Name="UseManagedClient" IsChecked="False">Use managed client</CheckBox>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button Click="Connect" Width="120" Margin="0,0,10,0">Connect</Button> <Button Click="Connect" Width="120" Margin="0,0,10,0">Connect</Button>
@@ -49,25 +77,46 @@
<PivotItem Header="Publish"> <PivotItem Header="Publish">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock>Topic:</TextBlock> <Grid>
<TextBox x:Name="Topic"></TextBox> <Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"></Setter>
<Setter Property="Margin" Value="0, 0, 10, 0"></Setter>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock>Payload:</TextBlock> <TextBlock Grid.Column="0" Grid.Row="0">Topic:</TextBlock>
<TextBox x:Name="Payload"></TextBox> <TextBox Grid.Column="1" Grid.Row="0" x:Name="Topic"></TextBox>
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="Text" IsChecked="True" GroupName="payload">Text</RadioButton>
<RadioButton x:Name="Base64" GroupName="payload">Base64</RadioButton>
</StackPanel>
<TextBlock>Retain:</TextBlock> <TextBlock Grid.Column="0" Grid.Row="1">Payload:</TextBlock>
<CheckBox x:Name="Retain"></CheckBox> <TextBox Grid.Column="1" Grid.Row="1" x:Name="Payload"></TextBox>
<TextBlock Grid.Column="0" Grid.Row="2">Payload format:</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal">
<RadioButton Margin="0,0,10,0" x:Name="PlainText" IsChecked="True" GroupName="payload">Plain text</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="Base64" GroupName="payload">Base64 string</RadioButton>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="3">QoS level:</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="3" Orientation="Horizontal">
<RadioButton Margin="0,0,10,0" IsChecked="True" GroupName="qos">0 (At most once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="QoS1" GroupName="qos">1 (At least once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="QoS2" GroupName="qos">2 (Exactly once)</RadioButton>
</StackPanel>
</Grid>
<CheckBox x:Name="Retain">Retain</CheckBox>
<TextBlock>QoS:</TextBlock>
<StackPanel Orientation="Horizontal">
<RadioButton Margin="0,0,10,0" IsChecked="True" GroupName="qos">0 (At most once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="QoS1" GroupName="qos">1 (At least once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="QoS2" GroupName="qos">2 (Exactly once)</RadioButton>
</StackPanel>
<Button Click="Publish" Width="120">Publish</Button> <Button Click="Publish" Width="120">Publish</Button>
</StackPanel> </StackPanel>
</PivotItem> </PivotItem>
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Security.Cryptography.Certificates; using Windows.Security.Cryptography.Certificates;
@@ -25,12 +26,15 @@ namespace MQTTnet.TestApp.UniversalWindows
private readonly ObservableCollection<IMqttClientSessionStatus> _sessions = new ObservableCollection<IMqttClientSessionStatus>(); private readonly ObservableCollection<IMqttClientSessionStatus> _sessions = new ObservableCollection<IMqttClientSessionStatus>();
private IMqttClient _mqttClient; private IMqttClient _mqttClient;
private IManagedMqttClient _managedMqttClient;
private IMqttServer _mqttServer; private IMqttServer _mqttServer;
public MainPage() public MainPage()
{ {
InitializeComponent(); InitializeComponent();
ClientId.Text = Guid.NewGuid().ToString("D");
MqttNetGlobalLogger.LogMessagePublished += OnTraceMessagePublished; MqttNetGlobalLogger.LogMessagePublished += OnTraceMessagePublished;
} }
@@ -81,7 +85,10 @@ namespace MQTTnet.TestApp.UniversalWindows
AllowUntrustedCertificates = true AllowUntrustedCertificates = true
}; };
var options = new MqttClientOptions { ClientId = ClientId.Text }; var options = new MqttClientOptions
{
ClientId = ClientId.Text
};
if (UseTcp.IsChecked == true) if (UseTcp.IsChecked == true)
{ {
@@ -127,12 +134,28 @@ namespace MQTTnet.TestApp.UniversalWindows
} }
var factory = new MqttFactory(); var factory = new MqttFactory();
_mqttClient = factory.CreateMqttClient();
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
_mqttClient.Connected += OnConnected;
_mqttClient.Disconnected += OnDisconnected;
await _mqttClient.ConnectAsync(options); if (UseManagedClient.IsChecked == true)
{
_managedMqttClient = factory.CreateManagedMqttClient();
_managedMqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
_managedMqttClient.Connected += OnConnected;
_managedMqttClient.Disconnected += OnDisconnected;
await _managedMqttClient.StartAsync(new ManagedMqttClientOptions
{
ClientOptions = options
});
}
else
{
_mqttClient = factory.CreateMqttClient();
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
_mqttClient.Connected += OnConnected;
_mqttClient.Disconnected += OnDisconnected;
await _mqttClient.ConnectAsync(options);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -171,11 +194,6 @@ namespace MQTTnet.TestApp.UniversalWindows
private async void Publish(object sender, RoutedEventArgs e) private async void Publish(object sender, RoutedEventArgs e)
{ {
if (_mqttClient == null)
{
return;
}
try try
{ {
var qos = MqttQualityOfServiceLevel.AtMostOnce; var qos = MqttQualityOfServiceLevel.AtMostOnce;
@@ -190,7 +208,7 @@ namespace MQTTnet.TestApp.UniversalWindows
} }
var payload = new byte[0]; var payload = new byte[0];
if (Text.IsChecked == true) if (PlainText.IsChecked == true)
{ {
payload = Encoding.UTF8.GetBytes(Payload.Text); payload = Encoding.UTF8.GetBytes(Payload.Text);
} }
@@ -207,7 +225,15 @@ namespace MQTTnet.TestApp.UniversalWindows
.WithRetainFlag(Retain.IsChecked == true) .WithRetainFlag(Retain.IsChecked == true)
.Build(); .Build();
await _mqttClient.PublishAsync(message); if (_mqttClient != null)
{
await _mqttClient.PublishAsync(message);
}
if (_managedMqttClient != null)
{
await _managedMqttClient.PublishAsync(message);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -219,7 +245,19 @@ namespace MQTTnet.TestApp.UniversalWindows
{ {
try try
{ {
await _mqttClient.DisconnectAsync(); if (_mqttClient != null)
{
await _mqttClient.DisconnectAsync();
_mqttClient.Dispose();
_mqttClient = null;
}
if (_managedMqttClient != null)
{
await _managedMqttClient.StopAsync();
_managedMqttClient.Dispose();
_managedMqttClient = null;
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -239,11 +277,6 @@ namespace MQTTnet.TestApp.UniversalWindows
private async void Subscribe(object sender, RoutedEventArgs e) private async void Subscribe(object sender, RoutedEventArgs e)
{ {
if (_mqttClient == null)
{
return;
}
try try
{ {
var qos = MqttQualityOfServiceLevel.AtMostOnce; var qos = MqttQualityOfServiceLevel.AtMostOnce;
@@ -257,7 +290,15 @@ namespace MQTTnet.TestApp.UniversalWindows
qos = MqttQualityOfServiceLevel.ExactlyOnce; qos = MqttQualityOfServiceLevel.ExactlyOnce;
} }
await _mqttClient.SubscribeAsync(new TopicFilter(SubscribeTopic.Text, qos)); if (_mqttClient != null)
{
await _mqttClient.SubscribeAsync(new TopicFilter(SubscribeTopic.Text, qos));
}
if (_managedMqttClient != null)
{
await _managedMqttClient.SubscribeAsync(new TopicFilter(SubscribeTopic.Text, qos));
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -267,14 +308,17 @@ namespace MQTTnet.TestApp.UniversalWindows
private async void Unsubscribe(object sender, RoutedEventArgs e) private async void Unsubscribe(object sender, RoutedEventArgs e)
{ {
if (_mqttClient == null)
{
return;
}
try try
{ {
await _mqttClient.UnsubscribeAsync(SubscribeTopic.Text); if (_mqttClient != null)
{
await _mqttClient.UnsubscribeAsync(SubscribeTopic.Text);
}
if (_managedMqttClient != null)
{
await _managedMqttClient.UnsubscribeAsync(SubscribeTopic.Text);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -398,6 +442,8 @@ namespace MQTTnet.TestApp.UniversalWindows
ListViewSessions.DataContext = _sessions; ListViewSessions.DataContext = _sessions;
} }
#region Wiki Code
private async Task WikiCode() private async Task WikiCode()
{ {
{ {
@@ -632,5 +678,7 @@ namespace MQTTnet.TestApp.UniversalWindows
await mqttClient.StartAsync(options); await mqttClient.StartAsync(options);
} }
} }
#endregion
} }
} }