Browse Source

Merge pull request #4 from chkr1011/master

merge last
release/3.x.x
Gerardo 6 years ago
committed by GitHub
parent
commit
449d8aca65
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 2578 additions and 592 deletions
  1. +3
    -0
      .gitignore
  2. +5
    -7
      Build/MQTTnet.AspNetCore.nuspec
  3. +47
    -0
      Build/MQTTnet.Extensions.Rpc.nuspec
  4. +33
    -23
      Build/MQTTnet.nuspec
  5. +25
    -11
      Build/build.ps1
  6. BIN
     
  7. +9
    -0
      Documents/Import_CodeSigningKey.md
  8. +31
    -0
      Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
  9. +101
    -0
      Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
  10. +1
    -0
      Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c
  11. +13
    -2
      Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
  12. +14
    -3
      Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj
  13. +10
    -8
      Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs
  14. +7
    -58
      Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
  15. +60
    -0
      Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs
  16. +14
    -7
      Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
  17. +4
    -4
      Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs
  18. +3
    -3
      Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs
  19. +255
    -0
      Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs
  20. +3
    -3
      Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs
  21. +3
    -3
      Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
  22. +8
    -3
      Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs
  23. +41
    -0
      Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs
  24. +4
    -4
      Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs
  25. +3
    -4
      Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs
  26. +10
    -0
      Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs
  27. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs
  28. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs
  29. +16
    -0
      Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs
  30. +4
    -6
      Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs
  31. +516
    -0
      Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs
  32. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs
  33. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs
  34. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs
  35. +5
    -2
      Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs
  36. +43
    -0
      Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs
  37. +5
    -6
      Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs
  38. +34
    -15
      Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs
  39. +3
    -1
      Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs
  40. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs
  41. +1
    -1
      Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs
  42. +2
    -2
      Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs
  43. +97
    -0
      Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs
  44. +32
    -0
      Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs
  45. +2
    -3
      Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs
  46. +21
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs
  47. +18
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs
  48. +13
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs
  49. +43
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs
  50. +14
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs
  51. +74
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs
  52. +23
    -0
      Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs
  53. +1
    -1
      Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs
  54. +1
    -1
      Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs
  55. +1
    -1
      Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs
  56. +1
    -1
      Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs
  57. +1
    -2
      Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs
  58. +36
    -0
      Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs
  59. +55
    -18
      Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs
  60. +101
    -39
      Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
  61. +16
    -16
      Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs
  62. +37
    -24
      Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs
  63. +37
    -16
      Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
  64. +72
    -21
      Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
  65. +2
    -2
      Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs
  66. +14
    -10
      Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs
  67. +24
    -13
      Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
  68. +19
    -0
      Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs
  69. +7
    -4
      Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs
  70. +2
    -2
      Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs
  71. +1
    -1
      Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs
  72. +172
    -79
      Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs
  73. +39
    -0
      Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs
  74. +2
    -2
      Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs
  75. +74
    -0
      Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs
  76. +13
    -21
      Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs
  77. +1
    -1
      Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs
  78. +2
    -2
      Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs
  79. +3
    -3
      Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs
  80. +25
    -0
      Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs
  81. +1
    -1
      Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs
  82. +27
    -98
      Frameworks/MQTTnet.NetStandard/MqttFactory.cs
  83. +7
    -0
      Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs
  84. +1
    -1
      Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs
  85. +2
    -2
      Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs
  86. +3
    -3
      Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs
  87. +3
    -3
      Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs
  88. +10
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs
  89. +2
    -2
      Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs
  90. +2
    -2
      Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs
  91. +2
    -2
      Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs
  92. +10
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs
  93. +10
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs
  94. +10
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs
  95. +10
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs
  96. +4
    -6
      Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs
  97. +4
    -4
      Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs
  98. +3
    -3
      Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs
  99. +12
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs
  100. +17
    -0
      Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs

+ 3
- 0
.gitignore View File

@@ -197,6 +197,7 @@ ClientBin/
*.dbmdl
*.dbproj.schemaview
*.jfm
# *.pfx
*.publishsettings
orleans.codegen.cs

@@ -290,3 +291,5 @@ __pycache__/
Build/nuget.exe
*.js
*.map

/Tests/MQTTnet.TestApp.NetCore/RetainedMessages.json

+ 5
- 7
Build/MQTTnet.AspNetCore.nuspec View File

@@ -2,7 +2,7 @@
<package >
<metadata>
<id>MQTTnet.AspNetCore</id>
<version>2.5.0</version>
<version>2.7.5</version>
<authors>Christian Kratky</authors>
<owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,14 +10,13 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is a support library to integrate MQTTnet into AspNetCore.</description>
<releaseNotes>initial version
<releaseNotes>* Updated to MQTTnet 2.7.5.
</releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</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</tags>
<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>
<dependencies>

<group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.5" />
<dependency id="MQTTnet" version="2.7.5" />
</group>
</dependencies>
</metadata>
@@ -25,6 +24,5 @@
<files>
<!-- .NET Standard 2.0 -->
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/>
</files>
</package>

+ 47
- 0
Build/MQTTnet.Extensions.Rpc.nuspec View File

@@ -0,0 +1,47 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>MQTTnet.Extensions.Rpc</id>
<version>2.7.5</version>
<authors>Christian Kratky</authors>
<owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/chkr1011/MQTTnet</projectUrl>
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is a extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
<releaseNotes>* Updated to MQTTnet 2.7.5.
</releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</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>
<dependencies>
<group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.7.5" />
</group>
<group targetFramework="netstandard1.3">
<dependency id="MQTTnet" version="2.7.5" />
</group>

<group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.7.5" />
</group>
<group targetFramework="uap10.0">
<dependency id="MQTTnet" version="2.7.5" />
</group>
<group targetFramework="net452">
<dependency id="MQTTnet" version="2.7.5" />
</group>

<group targetFramework="net461">
<dependency id="MQTTnet" version="2.7.5" />
</group>
</dependencies>
</metadata>

<files>
<!-- .NET Standard 2.0 -->
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/>
</files>
</package>

+ 33
- 23
Build/MQTTnet.nuspec View File

@@ -2,7 +2,7 @@
<package >
<metadata>
<id>MQTTnet</id>
<version>2.5.0</version>
<version>2.7.5</version>
<authors>Christian Kratky</authors>
<owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,49 +10,59 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<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>
<releaseNotes>* [Core] Merged the .NET Framwork and netstandard projects (Thanks to @JanEggers)
* [Core] Migrated the trace to a non-static approach (Breaking Change!)
* [Core] Added a builder for application messages using a fluent API
* [Core] Introduced CI (Thanks to @JanEggers)
* [Core] Added interfaces for publishing and receiving which applies to every client and server (Thanks to @ChristianRiedl)
* [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta)
* [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args
* [Client] Added a _TopicFilterBuilder_ using a fluent API (Namespace Changes!)
* [Client] Added several new options for the WebSocket channel (Thanks to @ChristianRiedl)
* [Client] Refactored the options and added a builder using a fluent API (Breaking Change!)
* [Client] Added more options for WebSocket connections like RequestHeaders, SubProtocol etc.
* [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl)
* [Server] Added support for a custom application message interceptor
* [Server] Fixed an issue with dropped connections on UWP (Thanks to @haeberle)
<releaseNotes> * [Client] Fixed a deadlock while the client disconnects.
* [Client] Fixed broken support for protocol version 3.1.0.
* [Server] The _MqttTcpServerAdapter_ is now added to the ASP.NET services.
* [Server] _MqttServerAdapter_ is renamed to _MqttTcpServerAdapter_ (BREAKING CHANGE!).
* [Server] The server no longer sends the will message of a client if the disconnect was clean (via _Disconnect_ packet).
* [Server] The application message interceptor now allows closing the connection.
* [Server] Added a new flag for the _ClientDisconnected_ event which contains a value indicating whether the disconnect was clean (via _Disconnect_ packet).
</releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</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</tags>
<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>
<dependencies>

<group targetFramework="netstandard1.3">
<dependency id="NETStandard.Library" version="1.6.1" />
<dependency id="NETStandard.Library" version="1.3.0" />
<dependency id="System.Net.Security" version="4.3.2" />
<dependency id="System.Net.WebSockets" version="4.3.0" />
<dependency id="System.Net.WebSockets.Client" version="4.3.1" />
</group>

<group targetFramework="netstandard2.0">
<dependency id="NETStandard.Library" version="2.0.0" />
<dependency id="System.Net.Security" version="4.3.2" />
<dependency id="System.Net.WebSockets" version="4.3.0" />
<dependency id="System.Net.WebSockets.Client" version="4.3.1" />
</group>
<group targetFramework="uap10.0">
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.0" />
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" />
</group>
<group targetFramework="net452">
</group>

<group targetFramework="net461">
</group>
</dependencies>
</metadata>

<files>
<!-- .NET Standard 1.3 -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.Core.*" target="lib\netstandard1.3\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/>
<!-- .NET Standard 2.0 -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard2.0\MQTTnet.*" target="lib\netstandard2.0\"/>

<!-- Universal Windows -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\uap10.0\MQTTnet.Core.*" target="lib\uap10.0\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\uap10.0\MQTTnet.*" target="lib\uap10.0\"/>

<!-- .NET Framework -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net451\MQTTnet.Core.*" target="lib\net451\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net451\MQTTnet.*" target="lib\net451\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/>
</files>
</package>

+ 25
- 11
Build/build.ps1 View File

@@ -1,6 +1,7 @@
param([string]$version)
param([string]$assemblyVersion, [string]$nugetVersion)

if ([string]::IsNullOrEmpty($version)) {$version = "0.0.1"}
if ([string]::IsNullOrEmpty($assemblyVersion)) {$assemblyVersion = "0.0.1"}
if ([string]::IsNullOrEmpty($nugetVersion)) {$nugetVersion = "0.0.1"}

$vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere'

@@ -8,14 +9,27 @@ $path = &$vswhere -latest -products * -requires Microsoft.Component.MSBuild -pro
if ($path) {
$msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe'

&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net451" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m
&dotnet build ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj -c="Release" /p:FileVersion=$version /p:AssemblyVersion=$version
# Build the core library
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
# Build the ASP.NET Core 2.0 extension
&$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"

Remove-Item .\NuGet -Force -Recurse
New-Item -ItemType Directory -Force -Path .\NuGet
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $version
.\NuGet.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $version
}
# Build the RPC extension
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m

Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue

New-Item -ItemType Directory -Force -Path .\NuGet
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
.\NuGet.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
.\NuGet.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
}

BIN
View File


+ 9
- 0
Documents/Import_CodeSigningKey.md View File

@@ -0,0 +1,9 @@
# Import codeSigningKey.pfx
In order to import the key for code signing on a new developer machine use the following command within the VS developer command line:

> sn –i codeSigningKey.pfx VS_KEY_EFCA4C5B6DFD4B4F

The container name may be different.

# Check if the assembly has a strong name
> sn -vf MQTTnet.dll

+ 31
- 0
Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
<Product />
<Company />
<Authors />
<PackageId />
<Version>0.0.0.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
</ItemGroup>

</Project>

+ 101
- 0
Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Internal;
using MQTTnet.Protocol;

namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClient : IDisposable
{
private const string ResponseTopic = "$MQTTnet.RPC/+/+/response";
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
private readonly IMqttClient _mqttClient;
private bool _isEnabled;

public MqttRpcClient(IMqttClient mqttClient)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));

_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
}

public async Task EnableAsync()
{
await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build());
_isEnabled = true;
}

public async Task DisableAsync()
{
await _mqttClient.UnsubscribeAsync(ResponseTopic);
_isEnabled = false;
}

public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (methodName == null) throw new ArgumentNullException(nameof(methodName));

if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#"))
{
throw new ArgumentException("The method name cannot contain /, + or #.");
}

if (!_isEnabled)
{
throw new InvalidOperationException("The RPC client is not enabled.");
}

var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
var responseTopic = requestTopic + "/response";

var requestMessage = new MqttApplicationMessageBuilder()
.WithTopic(requestTopic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.Build();

try
{
var tcs = new TaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, tcs))
{
throw new InvalidOperationException();
}

await _mqttClient.PublishAsync(requestMessage);
return await tcs.Task.TimeoutAfter(timeout);
}
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
}
}

private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource<byte[]> tcs))
{
return;
}

if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
{
return;
}

tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
}

public void Dispose()
{
foreach (var tcs in _waitingCalls)
{
tcs.Value.SetCanceled();
}

_waitingCalls.Clear();
}
}
}

+ 1
- 0
Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c View File

@@ -0,0 +1 @@


+ 13
- 2
Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs View File

@@ -1,7 +1,8 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Core.Server;
using System.Linq;
using MQTTnet.Server;

namespace MQTTnet.AspNetCore
{
@@ -14,8 +15,18 @@ namespace MQTTnet.AspNetCore
{
if (context.Request.Path == path && context.WebSockets.IsWebSocketRequest)
{
string subprotocol = null;

if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)
&& requestedSubProtocolValues.Count > 0
&& requestedSubProtocolValues.Any(v => v.ToLower() == "mqtt")
)
{
subprotocol = "mqtt";
}

var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync("mqtt"))
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subprotocol))
{
await adapter.AcceptWebSocketAsync(webSocket);
}


+ 14
- 3
Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj View File

@@ -2,12 +2,23 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
<Version>0.0.0.0</Version>
<Product />
<Company />
<Authors />
<PackageId />
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" />
</ItemGroup>

<ItemGroup>


+ 10
- 8
Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs View File

@@ -1,24 +1,26 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Server;

namespace MQTTnet.AspNetCore
{
public class MqttHostedServer : MqttServer, IHostedService
{
public MqttHostedServer(IOptions<MqttServerOptions> options, IEnumerable<IMqttServerAdapter> adapters, ILogger<MqttServer> logger, MqttClientSessionsManager clientSessionsManager)
: base(options, adapters, logger, clientSessionsManager)
private readonly IMqttServerOptions _options;

public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(adapters, logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}

public Task StartAsync(CancellationToken cancellationToken)
{
return StartAsync();
return StartAsync(_options);
}

public Task StopAsync(CancellationToken cancellationToken)


+ 7
- 58
Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs View File

@@ -1,27 +1,18 @@
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Server;
using MQTTnet.Implementations;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using MQTTnet.Server;

namespace MQTTnet.AspNetCore
{
public class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable
{
private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;

public MqttWebSocketServerAdapter(IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
{
_mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
}

public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;

public Task StartAsync(MqttServerOptions options)
public Task StartAsync(IMqttServerOptions options)
{
return Task.CompletedTask;
}
@@ -36,7 +27,7 @@ namespace MQTTnet.AspNetCore
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));

var channel = new MqttWebSocketServerChannel(webSocket);
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(channel);
var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketSerializer(), new MqttNetLogger());

var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
ClientAccepted?.Invoke(this, eventArgs);
@@ -47,47 +38,5 @@ namespace MQTTnet.AspNetCore
{
StopAsync();
}

private class MqttWebSocketServerChannel : IMqttCommunicationChannel, IDisposable
{
private readonly WebSocket _webSocket;

public MqttWebSocketServerChannel(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));

RawReceiveStream = new WebSocketStream(_webSocket);
}

public Stream SendStream => RawReceiveStream;
public Stream ReceiveStream => RawReceiveStream;
public Stream RawReceiveStream { get; }

public Task ConnectAsync()
{
return Task.CompletedTask;
}

public Task DisconnectAsync()
{
RawReceiveStream?.Dispose();

if (_webSocket == null)
{
return Task.CompletedTask;
}

return _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}

public void Dispose()
{
RawReceiveStream?.Dispose();
SendStream?.Dispose();
ReceiveStream?.Dispose();

_webSocket?.Dispose();
}
}
}
}

+ 60
- 0
Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs View File

@@ -0,0 +1,60 @@
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Implementations;

namespace MQTTnet.AspNetCore
{
public class MqttWebSocketServerChannel : IMqttChannel, IDisposable
{
private WebSocket _webSocket;

public MqttWebSocketServerChannel(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));

SendStream = new WebSocketStream(_webSocket);
ReceiveStream = SendStream;
}

public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }

public Task ConnectAsync()
{
return Task.CompletedTask;
}

public async Task DisconnectAsync()
{
if (_webSocket == null)
{
return;
}

try
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
finally
{
Dispose();
}
}

public void Dispose()
{
SendStream?.Dispose();
ReceiveStream?.Dispose();

_webSocket?.Dispose();

SendStream = null;
ReceiveStream = null;
_webSocket = null;
}
}
}

+ 14
- 7
Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs View File

@@ -1,22 +1,29 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Server;
using MQTTnet.Implementations;

namespace MQTTnet.AspNetCore
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options)
{
services.AddMqttServerServices();
if (options == null) throw new ArgumentNullException(nameof(options));

services.AddSingleton(options);
services.AddSingleton<IMqttNetLogger>(new MqttNetLogger());
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<MqttWebSocketServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());

return services;
}


MQTTnet.Core/Adapter/IMqttCommunicationAdapter.cs → Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs View File

@@ -2,12 +2,12 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;
using MQTTnet.Packets;
using MQTTnet.Serializer;

namespace MQTTnet.Core.Adapter
namespace MQTTnet.Adapter
{
public interface IMqttCommunicationAdapter
public interface IMqttChannelAdapter : IDisposable
{
IMqttPacketSerializer PacketSerializer { get; }


MQTTnet.Core/Adapter/IMqttServerAdapter.cs → Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs View File

@@ -1,14 +1,14 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Server;
using MQTTnet.Server;

namespace MQTTnet.Core.Adapter
namespace MQTTnet.Adapter
{
public interface IMqttServerAdapter
{
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;

Task StartAsync(MqttServerOptions options);
Task StartAsync(IMqttServerOptions options);
Task StopAsync();
}
}

+ 255
- 0
Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs View File

@@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Serializer;

namespace MQTTnet.Adapter
{
public sealed class MqttChannelAdapter : IMqttChannelAdapter
{
private const uint ErrorOperationAborted = 0x800703E3;
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config

private bool _isDisposed;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttNetLogger _logger;
private readonly IMqttChannel _channel;

public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
}

public IMqttPacketSerializer PacketSerializer { get; }

public Task ConnectAsync(TimeSpan timeout)
{
ThrowIfDisposed();
_logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);

return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
}

public Task DisconnectAsync(TimeSpan timeout)
{
ThrowIfDisposed();
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);

return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
}

public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
{
ThrowIfDisposed();

return ExecuteAndWrapExceptionAsync(async () =>
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
foreach (var packet in packets)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

if (packet == null)
{
continue;
}

_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);

var chunks = PacketSerializer.Serialize(packet);
foreach (var chunk in chunks)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
}
}

if (cancellationToken.IsCancellationRequested)
{
return;
}

if (timeout > TimeSpan.Zero)
{
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
finally
{
_semaphore.Release();
}
});
}

public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
ThrowIfDisposed();

MqttBasePacket packet = null;
await ExecuteAndWrapExceptionAsync(async () =>
{
ReceivedMqttPacket receivedMqttPacket = null;
try
{
if (timeout > TimeSpan.Zero)
{
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
}

if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}

packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body);
if (packet == null)
{
throw new MqttProtocolViolationException("Received malformed packet.");
}

_logger.Verbose<MqttChannelAdapter>("RX <<< {0}", packet);
}
finally
{
receivedMqttPacket?.Dispose();
}
}).ConfigureAwait(false);

return packet;
}

private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
{
var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
if (header == null)
{
return null;
}

if (header.BodyLength == 0)
{
return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false));
}

var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream();

var buffer = new byte[ReadBufferSize];
while (body.Length < header.BodyLength)
{
var bytesLeft = header.BodyLength - (int)body.Length;
if (bytesLeft > buffer.Length)
{
bytesLeft = buffer.Length;
}

var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false);

// Check if the client closed the connection before sending the full body.
if (readBytesCount == 0)
{
throw new MqttCommunicationException("Connection closed while reading remaining packet body.");
}

// Here is no need to await because internally only an array is used and no real I/O operation is made.
// Using async here will only generate overhead.
body.Write(buffer, 0, readBytesCount);
}

body.Seek(0L, SeekOrigin.Begin);

return new ReceivedMqttPacket(header, body);
}

private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action)
{
try
{
await action().ConfigureAwait(false);
}
catch (TaskCanceledException)
{
throw;
}
catch (OperationCanceledException)
{
throw;
}
catch (MqttCommunicationTimedOutException)
{
throw;
}
catch (MqttCommunicationException)
{
throw;
}
catch (COMException comException)
{
if ((uint)comException.HResult == ErrorOperationAborted)
{
throw new OperationCanceledException();
}

throw new MqttCommunicationException(comException);
}
catch (IOException exception)
{
if (exception.InnerException is SocketException socketException)
{
if (socketException.SocketErrorCode == SocketError.ConnectionAborted)
{
throw new OperationCanceledException();
}
}

throw new MqttCommunicationException(exception);
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
}

public void Dispose()
{
_isDisposed = true;
_semaphore?.Dispose();
_channel?.Dispose();
}

private void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(MqttChannelAdapter));
}
}
}
}

MQTTnet.Core/Adapter/MqttConnectingFailedException.cs → Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs View File

@@ -1,7 +1,7 @@
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;

namespace MQTTnet.Core.Adapter
namespace MQTTnet.Adapter
{
public class MqttConnectingFailedException : MqttCommunicationException
{

MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs → Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs View File

@@ -1,16 +1,16 @@
using System;
using System.Threading.Tasks;

namespace MQTTnet.Core.Adapter
namespace MQTTnet.Adapter
{
public class MqttServerAdapterClientAcceptedEventArgs : EventArgs
{
public MqttServerAdapterClientAcceptedEventArgs(IMqttCommunicationAdapter client)
public MqttServerAdapterClientAcceptedEventArgs(IMqttChannelAdapter client)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
}

public IMqttCommunicationAdapter Client { get; }
public IMqttChannelAdapter Client { get; }

public Task SessionTask { get; set; }
}

MQTTnet.Core/Adapter/ReceivedMqttPacket.cs → Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs View File

@@ -1,10 +1,10 @@
using System;
using System.IO;
using MQTTnet.Core.Packets;
using MQTTnet.Packets;

namespace MQTTnet.Core.Adapter
namespace MQTTnet.Adapter
{
public class ReceivedMqttPacket
public sealed class ReceivedMqttPacket : IDisposable
{
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body)
{
@@ -15,5 +15,10 @@ namespace MQTTnet.Core.Adapter
public MqttPacketHeader Header { get; }

public MemoryStream Body { get; }

public void Dispose()
{
Body?.Dispose();
}
}
}

+ 41
- 0
Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs View File

@@ -0,0 +1,41 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Protocol;

namespace MQTTnet
{
public static class ApplicationMessagePublisherExtensions
{
public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));

return publisher.PublishAsync(applicationMessages);
}

public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).Build());
}

public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));

return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build());
}

public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (publisher == null) throw new ArgumentNullException(nameof(publisher));
if (topic == null) throw new ArgumentNullException(nameof(topic));

return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}
}
}

MQTTnet.Core/Channel/IMqttCommunicationChannel.cs → Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs View File

@@ -1,13 +1,13 @@
using System.Threading.Tasks;
using System;
using System.IO;
using System.Threading.Tasks;

namespace MQTTnet.Core.Channel
namespace MQTTnet.Channel
{
public interface IMqttCommunicationChannel
public interface IMqttChannel : IDisposable
{
Stream SendStream { get; }
Stream ReceiveStream { get; }
Stream RawReceiveStream { get; }

Task ConnectAsync();
Task DisconnectAsync();

MQTTnet.Core/Client/IMqttClient.cs → Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs View File

@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{
bool IsConnected { get; }

@@ -16,6 +15,6 @@ namespace MQTTnet.Core.Client
Task DisconnectAsync();

Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topics);
}
}

+ 10
- 0
Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs View File

@@ -0,0 +1,10 @@
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;

namespace MQTTnet.Client
{
public interface IMqttClientAdapterFactory
{
IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger);
}
}

MQTTnet.Core/Client/IMqttClientChannelOptions.cs → Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public interface IMqttClientChannelOptions
{

MQTTnet.Core/Client/IMqttClientCredentials.cs → Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public interface IMqttClientCredentials
{

+ 16
- 0
Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs View File

@@ -0,0 +1,16 @@
using MQTTnet.Diagnostics;
using MQTTnet.ManagedClient;

namespace MQTTnet.Client
{
public interface IMqttClientFactory
{
IMqttClient CreateMqttClient();

IMqttClient CreateMqttClient(IMqttNetLogger logger);
IManagedMqttClient CreateManagedMqttClient();

IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger);
}
}

MQTTnet.Core/Client/IMqttClientOptions.cs → Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs View File

@@ -1,22 +1,20 @@
using System;
using MQTTnet.Core.Serializer;
using MQTTnet.Serializer;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public interface IMqttClientOptions
{
string ClientId { get; }

/// <summary>
/// The LogId is used to create a scope to correlate logging. If no value is provided the ClientId is used instead
/// </summary>
string LogId { get; }
IMqttClientCredentials Credentials { get; }
bool CleanSession { get; }
MqttApplicationMessage WillMessage { get; }
TimeSpan CommunicationTimeout { get; }
TimeSpan KeepAlivePeriod { get; }
TimeSpan? KeepAliveSendInterval { get; }

MqttProtocolVersion ProtocolVersion { get; }

IMqttClientChannelOptions ChannelOptions { get; }

+ 516
- 0
Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs View File

@@ -0,0 +1,516 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;

namespace MQTTnet.Client
{
public class MqttClient : IMqttClient
{
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly Stopwatch _sendTracker = new Stopwatch();
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger;

private IMqttClientOptions _options;
private bool _isReceivingPackets;
private CancellationTokenSource _cancellationTokenSource;
private Task _packetReceiverTask;
private Task _keepAliveMessageSenderTask;
private IMqttChannelAdapter _adapter;

public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger)
{
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));

_packetDispatcher = new MqttPacketDispatcher(logger);
}

public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;

public bool IsConnected { get; private set; }

public async Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");

ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");

try
{
_options = options;
_cancellationTokenSource = new CancellationTokenSource();
_packetIdentifierProvider.Reset();
_packetDispatcher.Reset();

_adapter = _adapterFactory.CreateClientAdapter(options, _logger);

_logger.Verbose<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Verbose<MqttClient>("Connection with server established.");

await StartReceivingPacketsAsync().ConfigureAwait(false);

var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
_logger.Verbose<MqttClient>("MQTT connection with server established.");

_sendTracker.Restart();

if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
StartSendingKeepAliveMessages();
}

IsConnected = true;
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));

_logger.Info<MqttClient>("Connected.");
return new MqttClientConnectResult(connectResponse.IsSessionPresent);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Error while connecting with server.");
await DisconnectInternalAsync(null, exception).ConfigureAwait(false);

throw;
}
}

public async Task DisconnectAsync()
{
if (!IsConnected)
{
return;
}

try
{
if (!_cancellationTokenSource.IsCancellationRequested)
{
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
}
}
finally
{
await DisconnectInternalAsync(null, null).ConfigureAwait(false);
}
}

public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));

ThrowIfNotConnected();

var subscribePacket = new MqttSubscribePacket
{
PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList()
};

var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false);

if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
{
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
}

return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
}

public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));

ThrowIfNotConnected();

var unsubscribePacket = new MqttUnsubscribePacket
{
PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList()
};

await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket).ConfigureAwait(false);
}

public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{
ThrowIfNotConnected();

var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key);

foreach (var qosGroup in packetGroups)
{
switch (qosGroup.Key)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(qosGroup.Cast<MqttBasePacket>().ToArray()).ConfigureAwait(false);
break;
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
foreach (var publishPacket in qosGroup)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false);
}

break;
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
foreach (var publishPacket in qosGroup)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();

var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
var pubRelPacket = new MqttPubRelPacket
{
PacketIdentifier = pubRecPacket.PacketIdentifier
};

await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false);
}

break;
}
default:
{
throw new InvalidOperationException();
}
}
}
}

public void Dispose()
{
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;

_adapter?.Dispose();
}

private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
{
var connectPacket = new MqttConnectPacket
{
ClientId = _options.ClientId,
Username = _options.Credentials?.Username,
Password = _options.Credentials?.Password,
CleanSession = _options.CleanSession,
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
WillMessage = willApplicationMessage
};

var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}

return response;
}

private void ThrowIfNotConnected()
{
if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
}

private void ThrowIfConnected(string message)
{
if (IsConnected) throw new MqttProtocolViolationException(message);
}

private async Task DisconnectInternalAsync(Task sender, Exception exception)
{
await _disconnectLock.WaitAsync();
try
{
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
{
return;
}

_cancellationTokenSource.Cancel(false);
}
catch (Exception adapterException)
{
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter.");
}
finally
{
_disconnectLock.Release();
}

var clientWasConnected = IsConnected;
IsConnected = false;

try
{
if (_packetReceiverTask != null && _packetReceiverTask != sender)
{
_packetReceiverTask.Wait();
}

if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender)
{
_keepAliveMessageSenderTask.Wait();
}

if (_adapter != null)
{
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
}
_logger.Verbose<MqttClient>("Disconnected from adapter.");
}
catch (Exception adapterException)
{
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter.");
}
finally
{
_adapter?.Dispose();
_adapter = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;

_logger.Info<MqttClient>("Disconnected.");
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
}
}

private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
{
try
{
if (packet is MqttPublishPacket publishPacket)
{
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false);
return;
}

if (packet is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false);
return;
}

if (packet is MqttDisconnectPacket)
{
await DisconnectAsync().ConfigureAwait(false);
return;
}

if (packet is MqttPubRelPacket pubRelPacket)
{
await ProcessReceivedPubRelPacket(pubRelPacket).ConfigureAwait(false);
return;
}

_packetDispatcher.Dispatch(packet);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
}
}

private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{
try
{
var applicationMessage = publishPacket.ToApplicationMessage();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
}
}

private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
{
if (_cancellationTokenSource.IsCancellationRequested)
{
return Task.FromResult(0);
}

if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return Task.FromResult(0);
}

if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}

if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}

throw new MqttCommunicationException("Received a not supported QoS level.");
}

private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{
var response = new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier
};

return SendAsync(response);
}

private Task SendAsync(params MqttBasePacket[] packets)
{
_sendTracker.Restart();
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets);
}

private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{
ushort? identifier = null;
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
{
identifier = requestPacketWithIdentifier.PacketIdentifier;
}

var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
await SendAsync(requestPacket).ConfigureAwait(false);

return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
}

private async Task SendKeepAliveMessagesAsync()
{
_logger.Verbose<MqttClient>("Start sending keep alive packets.");

try
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
if (_options.KeepAliveSendInterval.HasValue)
{
keepAliveSendInterval = _options.KeepAliveSendInterval.Value;
}

if (_sendTracker.Elapsed > keepAliveSendInterval)
{
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
}

await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
}
else
{
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
}
finally
{
_logger.Verbose<MqttClient>("Stopped sending keep alive packets.");
}
}

private async Task ReceivePacketsAsync()
{
_logger.Verbose<MqttClient>("Start receiving packets.");

try
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
_isReceivingPackets = true;

var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);

if (_cancellationTokenSource.Token.IsCancellationRequested)
{
return;
}

StartProcessReceivedPacket(packet);
}
}
catch (Exception exception)
{
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
}
else
{
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
}

await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
}
finally
{
_logger.Verbose<MqttClient>("Stopped receiving packets.");
}
}

private void StartProcessReceivedPacket(MqttBasePacket packet)
{
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token);
}

private async Task StartReceivingPacketsAsync()
{
_isReceivingPackets = false;

_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);

while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false);
}
}

private void StartSendingKeepAliveMessages()
{
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token);
}
}
}

MQTTnet.Core/Client/MqttClientConnectResult.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientConnectResult
{

MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs View File

@@ -1,6 +1,6 @@
using System;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientConnectedEventArgs : EventArgs
{

MQTTnet.Core/Client/MqttClientCredentials.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientCredentials : IMqttClientCredentials
{

MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs View File

@@ -1,14 +1,17 @@
using System;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientDisconnectedEventArgs : EventArgs
{
public MqttClientDisconnectedEventArgs(bool clientWasConnected)
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception)
{
ClientWasConnected = clientWasConnected;
Exception = exception;
}

public bool ClientWasConnected { get; }

public Exception Exception { get; }
}
}

+ 43
- 0
Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Protocol;

namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));

return client.SubscribeAsync(topicFilters.ToList());
}

public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));

return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}

public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topic == null) throw new ArgumentNullException(nameof(topic));

return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
}

public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));

return client.UnsubscribeAsync(topicFilters.ToList());
}
}
}

MQTTnet.Core/Client/MqttClientOptions.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs View File

@@ -1,7 +1,7 @@
using System;
using MQTTnet.Core.Serializer;
using MQTTnet.Serializer;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientOptions : IMqttClientOptions
{
@@ -9,14 +9,13 @@ namespace MQTTnet.Core.Client

public string ClientId { get; set; } = Guid.NewGuid().ToString("N");

/// <inheritdoc />
public string LogId { get; set; }

public bool CleanSession { get; set; } = true;

public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();

public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);

public TimeSpan? KeepAliveSendInterval { get; set; }

public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);


MQTTnet.Core/Client/MqttClientOptionsBuilder.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs View File

@@ -1,7 +1,8 @@
using System;
using MQTTnet.Core.Serializer;
using System.Linq;
using MQTTnet.Serializer;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientOptionsBuilder
{
@@ -11,15 +12,15 @@ namespace MQTTnet.Core.Client

private MqttClientTlsOptions _tlsOptions;

public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion protocolVersion)
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{
_options.ProtocolVersion = protocolVersion;
_options.ProtocolVersion = value;
return this;
}

public MqttClientOptionsBuilder WithCommunicationTimeout(TimeSpan communicationTimeout)
public MqttClientOptionsBuilder WithCommunicationTimeout(TimeSpan value)
{
_options.CommunicationTimeout = communicationTimeout;
_options.CommunicationTimeout = value;
return this;
}

@@ -29,21 +30,21 @@ namespace MQTTnet.Core.Client
return this;
}

public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan keepAlivePeriod)
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = keepAlivePeriod;
_options.KeepAlivePeriod = value;
return this;
}

public MqttClientOptionsBuilder WithClientId(string clientId)
public MqttClientOptionsBuilder WithClientId(string value)
{
_options.ClientId = clientId;
_options.ClientId = value;
return this;
}

public MqttClientOptionsBuilder WithWillMessage(MqttApplicationMessage applicationMessage)
public MqttClientOptionsBuilder WithWillMessage(MqttApplicationMessage value)
{
_options.WillMessage = applicationMessage;
_options.WillMessage = value;
return this;
}

@@ -79,6 +80,24 @@ namespace MQTTnet.Core.Client
return this;
}

public MqttClientOptionsBuilder WithTls(
bool allowUntrustedCertificates = false,
bool ignoreCertificateChainErrors = false,
bool ignoreCertificateRevocationErrors = false,
params byte[][] certificates)
{
_tlsOptions = new MqttClientTlsOptions
{
UseTls = true,
AllowUntrustedCertificates = allowUntrustedCertificates,
IgnoreCertificateChainErrors = ignoreCertificateChainErrors,
IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors,
Certificates = certificates?.ToList()
};

return this;
}

public MqttClientOptionsBuilder WithTls()
{
_tlsOptions = new MqttClientTlsOptions
@@ -100,11 +119,11 @@ namespace MQTTnet.Core.Client

if (_tcpOptions != null)
{
_options.ChannelOptions = _tcpOptions;
_tcpOptions.TlsOptions = _tlsOptions;
}
else
else if (_webSocketOptions != null)
{
_options.ChannelOptions = _webSocketOptions;
_webSocketOptions.TlsOptions = _tlsOptions;
}
}


MQTTnet.Core/Client/MqttClientTcpOptions.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientTcpOptions : IMqttClientChannelOptions
{
@@ -6,6 +6,8 @@

public int? Port { get; set; }

public int BufferSize { get; set; } = 20 * 4096;

public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
}
}

MQTTnet.Core/Client/MqttClientTcpOptionsExtensions.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs View File

@@ -1,6 +1,6 @@
using System;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public static class MqttClientTcpOptionsExtensions
{

MQTTnet.Core/Client/MqttClientTlsOptions.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientTlsOptions
{

MQTTnet.Core/Client/MqttClientWebSocketOptions.cs → Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Net;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttClientWebSocketOptions : IMqttClientChannelOptions
{
@@ -9,7 +9,7 @@ namespace MQTTnet.Core.Client

public IDictionary<string, string> RequestHeaders { get; set; }

public ICollection<string> SubProtocols { get; set; }
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };

public CookieContainer CookieContainer { get; set; }


+ 97
- 0
Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;

namespace MQTTnet.Client
{
public class MqttPacketDispatcher
{
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly IMqttNetLogger _logger;

public MqttPacketDispatcher(IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout)
{
var packetAwaiter = AddPacketAwaiter(responseType, identifier);
try
{
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
}
catch (MqttCommunicationTimedOutException)
{
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name);
throw;
}
finally
{
RemovePacketAwaiter(responseType, identifier);
}
}

public void Dispatch(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));

var type = packet.GetType();

if (_awaiters.TryGetValue(type, out var byId))
{
ushort? identifier = 0;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier)
{
identifier = packetWithIdentifier.PacketIdentifier;
}

if (byId.TryRemove(identifier.Value, out var tcs))
{
tcs.TrySetResult(packet);
return;
}
}

throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
}

public void Reset()
{
_awaiters.Clear();
}

private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier)
{
var tcs = new TaskCompletionSource<MqttBasePacket>();

if (!identifier.HasValue)
{
identifier = 0;
}

var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
if (!byId.TryAdd(identifier.Value, tcs))
{
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
}

return tcs;
}

private void RemovePacketAwaiter(Type responseType, ushort? identifier)
{
if (!identifier.HasValue)
{
identifier = 0;
}

var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId.TryRemove(identifier.Value, out var _);
}
}
}

+ 32
- 0
Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs View File

@@ -0,0 +1,32 @@
namespace MQTTnet.Client
{
public class MqttPacketIdentifierProvider
{
private readonly object _syncRoot = new object();
private ushort _value;

public void Reset()
{
lock (_syncRoot)
{
_value = 0;
}
}

public ushort GetNewPacketIdentifier()
{
lock (_syncRoot)
{
_value++;

if (_value == 0)
{
// As per official MQTT documentation the package identifier should never be 0.
_value = 1;
}

return _value;
}
}
}
}

MQTTnet.Core/Client/MqttSubscribeResult.cs → Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs View File

@@ -1,7 +1,6 @@
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using MQTTnet.Protocol;

namespace MQTTnet.Core.Client
namespace MQTTnet.Client
{
public class MqttSubscribeResult
{

+ 21
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs View File

@@ -0,0 +1,21 @@
using System;

namespace MQTTnet.Diagnostics
{
public interface IMqttNetLogger
{
event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;

void Verbose<TSource>(string message, params object[] parameters);

void Info<TSource>(string message, params object[] parameters);

void Warning<TSource>(Exception exception, string message, params object[] parameters);

void Warning<TSource>(string message, params object[] parameters);

void Error<TSource>(Exception exception, string message, params object[] parameters);

void Error<TSource>(string message, params object[] parameters);
}
}

+ 18
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs View File

@@ -0,0 +1,18 @@
using System;

namespace MQTTnet.Diagnostics
{
public static class MqttNetGlobalLogger
{
public static event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;

public static bool HasListeners => LogMessagePublished != null;

public static void Publish(MqttNetLogMessage logMessage)
{
if (logMessage == null) throw new ArgumentNullException(nameof(logMessage));

LogMessagePublished?.Invoke(null, new MqttNetLogMessagePublishedEventArgs(logMessage));
}
}
}

+ 13
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs View File

@@ -0,0 +1,13 @@
namespace MQTTnet.Diagnostics
{
public enum MqttNetLogLevel
{
Verbose,

Info,

Warning,

Error
}
}

+ 43
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs View File

@@ -0,0 +1,43 @@
using System;

namespace MQTTnet.Diagnostics
{
public sealed class MqttNetLogMessage
{
public MqttNetLogMessage(string logId, DateTime timestamp, int threadId, string source, MqttNetLogLevel level, string message, Exception exception)
{
LogId = logId;
Timestamp = timestamp;
ThreadId = threadId;
Source = source;
Level = level;
Message = message;
Exception = exception;
}

public string LogId { get; }

public DateTime Timestamp { get; }

public int ThreadId { get; }

public string Source { get; }

public MqttNetLogLevel Level { get; }

public string Message { get; }

public Exception Exception { get; }

public override string ToString()
{
var result = $"[{Timestamp:O}] [{LogId}] [{ThreadId}] [{Source}] [{Level}]: {Message}";
if (Exception != null)
{
result += Environment.NewLine + Exception;
}

return result;
}
}
}

+ 14
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs View File

@@ -0,0 +1,14 @@
using System;

namespace MQTTnet.Diagnostics
{
public sealed class MqttNetLogMessagePublishedEventArgs : EventArgs
{
public MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage)
{
TraceMessage = logMessage ?? throw new ArgumentNullException(nameof(logMessage));
}

public MqttNetLogMessage TraceMessage { get; }
}
}

+ 74
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs View File

@@ -0,0 +1,74 @@
using System;

namespace MQTTnet.Diagnostics
{
public class MqttNetLogger : IMqttNetLogger
{
private readonly string _logId;

public MqttNetLogger(string logId = null)
{
_logId = logId;
}

public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished;

public void Verbose<TSource>(string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Verbose, null, message, parameters);
}

public void Info<TSource>(string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Info, null, message, parameters);
}

public void Warning<TSource>(Exception exception, string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Warning, exception, message, parameters);
}

public void Warning<TSource>(string message, params object[] parameters)
{
Warning<TSource>(null, message, parameters);
}

public void Error<TSource>(Exception exception, string message, params object[] parameters)
{
Publish<TSource>(MqttNetLogLevel.Error, exception, message, parameters);
}

public void Error<TSource>(string message, params object[] parameters)
{
Warning<TSource>(null, message, parameters);
}

private void Publish<TSource>(MqttNetLogLevel logLevel, Exception exception, string message, object[] parameters)
{
var hasLocalListeners = LogMessagePublished != null;
var hasGlobalListeners = MqttNetGlobalLogger.HasListeners;

if (!hasLocalListeners && !hasGlobalListeners)
{
return;
}

if (parameters.Length > 0)
{
message = string.Format(message, parameters);
}

var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, typeof(TSource).Name, logLevel, message, exception);

if (hasGlobalListeners)
{
MqttNetGlobalLogger.Publish(traceMessage);
}

if (hasLocalListeners)
{
LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(traceMessage));
}
}
}
}

+ 23
- 0
Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs View File

@@ -0,0 +1,23 @@
namespace MQTTnet.Diagnostics
{
public static class TargetFrameworkInfoProvider
{
public static string TargetFramework
{
get
{
#if NET452
return "net452";
#elif NET461
return "net461";
#elif NETSTANDARD1_3
return "netstandard1.3";
#elif NETSTANDARD2_0
return "netstandard2.0";
#elif WINDOWS_UWP
return "uap10.0";
#endif
}
}
}
}

MQTTnet.Core/Exceptions/MqttCommunicationException.cs → Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs View File

@@ -1,6 +1,6 @@
using System;

namespace MQTTnet.Core.Exceptions
namespace MQTTnet.Exceptions
{
public class MqttCommunicationException : Exception
{

MQTTnet.Core/Exceptions/MqttCommunicationTimedOutException.cs → Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Exceptions
namespace MQTTnet.Exceptions
{
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException
{

MQTTnet.Core/Exceptions/MqttProtocolViolationException.cs → Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs View File

@@ -1,6 +1,6 @@
using System;

namespace MQTTnet.Core.Exceptions
namespace MQTTnet.Exceptions
{
public sealed class MqttProtocolViolationException : Exception
{

MQTTnet.Core/IApplicationMessagePublisher.cs → Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MQTTnet.Core
namespace MQTTnet
{
public interface IApplicationMessagePublisher
{

MQTTnet.Core/IApplicationMessageReceiver.cs → Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs View File

@@ -1,7 +1,6 @@
using System;
using MQTTnet.Core.Client;

namespace MQTTnet.Core
namespace MQTTnet
{
public interface IApplicationMessageReceiver
{

+ 36
- 0
Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs View File

@@ -0,0 +1,36 @@
using System;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;

namespace MQTTnet.Implementations
{
public class MqttClientAdapterFactory : IMqttClientAdapterFactory
{
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger)
{
if (options == null) throw new ArgumentNullException(nameof(options));

var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion };

switch (options.ChannelOptions)
{
case MqttClientTcpOptions tcpOptions:
{
return new MqttChannelAdapter(new MqttTcpChannel(tcpOptions), serializer, logger);
}

case MqttClientWebSocketOptions webSocketOptions:
{
return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), serializer, logger);
}

default:
{
throw new NotSupportedException();
}
}
}
}
}

Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttTcpChannel.cs → Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs View File

@@ -1,5 +1,4 @@
#if NET451 || NETSTANDARD1_3
#else
#if WINDOWS_UWP
using System;
using System.Collections.Generic;
using System.IO;
@@ -9,19 +8,26 @@ using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Security.Cryptography.Certificates;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Channel;
using MQTTnet.Client;

namespace MQTTnet.Implementations
{
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
public sealed class MqttTcpChannel : IMqttChannel
{
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.

private readonly int _bufferSize = BufferSize;
private readonly MqttClientTcpOptions _options;

private StreamSocket _socket;

public MqttTcpChannel(MqttClientTcpOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = _options.BufferSize;
}

public MqttTcpChannel(StreamSocket socket)
@@ -33,7 +39,6 @@ namespace MQTTnet.Implementations

public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public Stream RawReceiveStream { get; private set; }

public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }

@@ -71,24 +76,56 @@ namespace MQTTnet.Implementations

public void Dispose()
{
RawReceiveStream?.Dispose();
RawReceiveStream = null;

SendStream?.Dispose();
SendStream = null;
try
{
SendStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
SendStream = null;
}

ReceiveStream?.Dispose();
ReceiveStream = null;
try
{
ReceiveStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}

_socket?.Dispose();
_socket = null;
try
{
_socket?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_socket = null;
}
}

private void CreateStreams()
{
SendStream = _socket.OutputStream.AsStreamForWrite();
ReceiveStream = _socket.InputStream.AsStreamForRead();
RawReceiveStream = ReceiveStream;
SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize);
}

private static Certificate LoadCertificate(MqttClientTcpOptions options)

Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttTcpChannel.cs → Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs View File

@@ -1,29 +1,31 @@
#if NET451 || NETSTANDARD1_3

#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using System.IO;
using System.Linq;
using MQTTnet.Channel;
using MQTTnet.Client;

namespace MQTTnet.Implementations
{
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
public sealed class MqttTcpChannel : IMqttChannel
{
private readonly MqttClientTcpOptions _options;

//todo: this can be used with min dependency NetStandard1.6
#if NET45
#if NET452 || NET461
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.

private readonly int _bufferSize = BufferSize;
#else
private readonly int _bufferSize = 0;
#endif

private readonly MqttClientTcpOptions _options;

private Socket _socket;
private SslStream _sslStream;

@@ -33,6 +35,7 @@ namespace MQTTnet.Implementations
public MqttTcpChannel(MqttClientTcpOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = options.BufferSize;
}

/// <summary>
@@ -44,12 +47,11 @@ namespace MQTTnet.Implementations
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream;

CreateStreams(socket, sslStream);
CreateStreams();
}

public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public Stream RawReceiveStream { get; private set; }

public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }

@@ -60,8 +62,7 @@ namespace MQTTnet.Implementations
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}

//todo: else brach can be used with min dependency NET46
#if NET451
#if NET452 || NET461
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
#else
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false);
@@ -70,10 +71,10 @@ namespace MQTTnet.Implementations
if (_options.TlsOptions.UseTls)
{
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
}
CreateStreams(_socket, _sslStream);
CreateStreams();
}

public Task DisconnectAsync()
@@ -84,11 +85,70 @@ namespace MQTTnet.Implementations

public void Dispose()
{
_socket?.Dispose();
_socket = null;
var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream);

try
{
SendStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
SendStream = null;
}

try
{
if (!oneStreamIsUsed)
{
ReceiveStream?.Dispose();
}
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}

try
{
_sslStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_sslStream = null;
}

_sslStream?.Dispose();
_sslStream = null;
try
{
_socket?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_socket = null;
}
}

private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
@@ -122,40 +182,42 @@ namespace MQTTnet.Implementations
return _options.TlsOptions.AllowUntrustedCertificates;
}

private static X509CertificateCollection LoadCertificates(MqttClientTcpOptions options)
private X509CertificateCollection LoadCertificates()
{
var certificates = new X509CertificateCollection();
if (options.TlsOptions.Certificates == null)
if (_options.TlsOptions.Certificates == null)
{
return certificates;
}

foreach (var certificate in options.TlsOptions.Certificates)
foreach (var certificate in _options.TlsOptions.Certificates)
{
certificates.Add(new X509Certificate(certificate));
certificates.Add(new X509Certificate2(certificate));
}

return certificates;
}

private void CreateStreams(Socket socket, Stream sslStream)
private void CreateStreams()
{
RawReceiveStream = sslStream ?? new NetworkStream(socket);


//cannot use this as default buffering prevents from receiving the first connect message
//need two streams otherwise read and write have to be synchronized

//todo: if branch can be used with min dependency NetStandard1.6
#if NET45
SendStream = new BufferedStream(RawReceiveStream, BufferSize);
ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize);
Stream stream;
if (_sslStream != null)
{
stream = _sslStream;
}
else
{
stream = new NetworkStream(_socket, true);
}
#if NET452 || NET461
SendStream = new BufferedStream(stream, _bufferSize);
ReceiveStream = new BufferedStream(stream, _bufferSize);
#else
SendStream = RawReceiveStream;
ReceiveStream = RawReceiveStream;
SendStream = stream;
ReceiveStream = stream;
#endif
}

}
}
#endif
#endif

Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttServerAdapter.cs → Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs View File

@@ -1,29 +1,27 @@
#if NET451 || NETSTANDARD1_3
#else
#if WINDOWS_UWP
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server;
using Windows.Networking.Sockets;
using Microsoft.Extensions.Logging;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using MQTTnet.Server;

namespace MQTTnet.Implementations
{
public class MqttServerAdapter : IMqttServerAdapter, IDisposable
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
{
private readonly ILogger<MqttServerAdapter> _logger;
private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;
private readonly IMqttNetLogger _logger;
private StreamSocketListener _defaultEndpointSocket;

public MqttServerAdapter(ILogger<MqttServerAdapter> logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
public MqttTcpServerAdapter(IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
}

public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;

public async Task StartAsync(MqttServerOptions options)
public async Task StartAsync(IMqttServerOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));

@@ -32,7 +30,6 @@ namespace MQTTnet.Implementations
if (options.DefaultEndpointOptions.IsEnabled)
{
_defaultEndpointSocket = new StreamSocketListener();
_defaultEndpointSocket.Control.NoDelay = true;
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
}
@@ -45,6 +42,11 @@ namespace MQTTnet.Implementations

public Task StopAsync()
{
if (_defaultEndpointSocket != null)
{
_defaultEndpointSocket.ConnectionReceived -= AcceptDefaultEndpointConnectionsAsync;
}

_defaultEndpointSocket?.Dispose();
_defaultEndpointSocket = null;

@@ -60,14 +62,12 @@ namespace MQTTnet.Implementations
{
try
{
args.Socket.Control.NoDelay = true;

var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(args.Socket));
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
catch (Exception exception)
{
_logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint.");
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint.");
}
}
}

Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttServerAdapter.cs → Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs View File

@@ -1,5 +1,4 @@
#if NET451 || NETSTANDARD1_3

#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
using System;
using System.Net;
using System.Net.Security;
@@ -8,31 +7,30 @@ using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server;
using Microsoft.Extensions.Logging;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Serializer;
using MQTTnet.Server;

namespace MQTTnet.Implementations
{
public class MqttServerAdapter : IMqttServerAdapter, IDisposable
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable
{
private readonly ILogger<MqttServerAdapter> _logger;
private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;
private readonly IMqttNetLogger _logger;

private CancellationTokenSource _cancellationTokenSource;
private Socket _defaultEndpointSocket;
private Socket _tlsEndpointSocket;
private X509Certificate2 _tlsCertificate;

public MqttServerAdapter(ILogger<MqttServerAdapter> logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
public MqttTcpServerAdapter(IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
}

public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;

public Task StartAsync(MqttServerOptions options)
public Task StartAsync(IMqttServerOptions options)
{
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started.");

@@ -41,10 +39,10 @@ namespace MQTTnet.Implementations
if (options.DefaultEndpointOptions.IsEnabled)
{
_defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
_defaultEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetDefaultEndpointPort()));
_defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort()));
_defaultEndpointSocket.Listen(options.ConnectionBacklog);

Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
}

if (options.TlsEndpointOptions.IsEnabled)
@@ -61,10 +59,10 @@ namespace MQTTnet.Implementations
}

_tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
_tlsEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetTlsEndpointPort()));
_tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort()));
_tlsEndpointSocket.Listen(options.ConnectionBacklog);

Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
}

return Task.FromResult(0);
@@ -99,19 +97,27 @@ namespace MQTTnet.Implementations
try
{
//todo: else branch can be used with min dependency NET46
#if NET451
#if NET452 || NET461
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
#else
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
#endif
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, null));

var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
catch (ObjectDisposedException)
{
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
}
catch (Exception exception)
{
_logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint.");
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
{
return;
}

//excessive CPU consumed if in endless loop of socket errors
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint.");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
@@ -123,7 +129,7 @@ namespace MQTTnet.Implementations
{
try
{
#if NET451
#if NET452 || NET461
var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false);
#else
var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false);
@@ -131,15 +137,22 @@ namespace MQTTnet.Implementations

var sslStream = new SslStream(new NetworkStream(clientSocket));
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream));
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
catch (ObjectDisposedException)
{
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
}
catch (Exception exception)
{
_logger.LogError(new EventId(), exception, "Error while accepting connection at TLS endpoint.");
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
{
return;
}

//excessive CPU consumed if in endless loop of socket errors
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at TLS endpoint.");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}

+ 37
- 16
Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs View File

@@ -1,16 +1,20 @@
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using System;
using System;
using System.IO;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;

namespace MQTTnet.Implementations
{
public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable
public sealed class MqttWebSocketChannel : IMqttChannel
{
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.

private readonly MqttClientWebSocketOptions _options;
private ClientWebSocket _webSocket;

@@ -19,20 +23,26 @@ namespace MQTTnet.Implementations
_options = options ?? throw new ArgumentNullException(nameof(options));
}

public Stream SendStream => RawReceiveStream;
public Stream ReceiveStream => RawReceiveStream;
public Stream RawReceiveStream { get; private set; }
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }

public async Task ConnectAsync()
{
var uri = _options.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{
uri = "ws://" + uri;
if (_options.TlsOptions?.UseTls == false)
{
uri = "ws://" + uri;
}
else
{
uri = "wss://" + uri;
}
}

_webSocket = new ClientWebSocket();

if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
@@ -63,14 +73,14 @@ namespace MQTTnet.Implementations
}
}

await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
RawReceiveStream = new WebSocketStream(_webSocket);
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);

SendStream = new WebSocketStream(_webSocket);
ReceiveStream = SendStream;
}

public async Task DisconnectAsync()
{
RawReceiveStream = null;

if (_webSocket == null)
{
return;
@@ -80,12 +90,23 @@ namespace MQTTnet.Implementations
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
}

Dispose();
}

public void Dispose()
{
_webSocket?.Dispose();
_webSocket = null;
try
{
_webSocket?.Dispose();
}
catch (ObjectDisposedException)
{
}
finally
{
_webSocket = null;
}
}
}
}

+ 72
- 21
Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs View File

@@ -1,16 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Exceptions;
using MQTTnet.Exceptions;

namespace MQTTnet.Implementations
{
public class WebSocketStream : Stream
{
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
private readonly WebSocket _webSocket;
public WebSocketStream(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
@@ -34,43 +38,69 @@ namespace MQTTnet.Implementations
{
}

public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
}

public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}

public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var currentOffset = offset;
var targetOffset = offset + count;
while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset)
var bytesRead = 0;

// Use existing date from buffer.
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}

if (count == 0)
{
return bytesRead;
}

// Fetch new data if the buffer is not full.
while (_webSocket.State == WebSocketState.Open)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
currentOffset += response.Count;
count -= response.Count;
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);

if (response.MessageType == WebSocketMessageType.Close)
while (count > 0 && _buffer.Any())
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}

if (count == 0)
{
return bytesRead;
}
}

if (_webSocket.State == WebSocketState.Closed)
{
throw new MqttCommunicationException( "connection closed" );
throw new MqttCommunicationException("WebSocket connection closed.");
}

return currentOffset - offset;
return bytesRead;
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
}

public override int Read(byte[] buffer, int offset, int count)
public override void Write(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}

public override void Write(byte[] buffer, int offset, int count)
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
}

public override long Seek(long offset, SeekOrigin origin)
@@ -82,5 +112,26 @@ namespace MQTTnet.Implementations
{
throw new NotSupportedException();
}

private async Task FetchChunkAsync(CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);

if (response.MessageType == WebSocketMessageType.Close)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
}
else if (response.MessageType == WebSocketMessageType.Binary)
{
for (var i = 0; i < response.Count; i++)
{
_buffer.Enqueue(_chunckBuffer[i]);
}
}
else if (response.MessageType == WebSocketMessageType.Text)
{
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
}
}
}
}

MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs → Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs View File

@@ -1,6 +1,6 @@
using MQTTnet.Core.Packets;
using MQTTnet.Packets;

namespace MQTTnet.Core.Internal
namespace MQTTnet.Internal
{
internal static class MqttApplicationMessageExtensions
{

MQTTnet.Core/Internal/TaskExtensions.cs → Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs View File

@@ -1,21 +1,23 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Exceptions;
using MQTTnet.Exceptions;

namespace MQTTnet.Core.Internal
namespace MQTTnet.Internal
{
public static class TaskExtensions
{
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
if (task == null) throw new ArgumentNullException(nameof(task));

using (var timeoutCts = new CancellationTokenSource())
{
try
{
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
var timeoutTask = Task.Delay(timeout, timeoutCts.Token);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
@@ -28,23 +30,25 @@ namespace MQTTnet.Core.Internal

if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception.GetBaseException());
throw new MqttCommunicationException(task.Exception?.GetBaseException());
}
}
finally
{
cancellationTokenSource.Cancel();
timeoutCts.Cancel();
}
}
}

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
if (task == null) throw new ArgumentNullException(nameof(task));

using (var timeoutCts = new CancellationTokenSource())
{
try
{
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
var timeoutTask = Task.Delay(timeout, timeoutCts.Token);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);

if (finishedTask == timeoutTask)
@@ -66,7 +70,7 @@ namespace MQTTnet.Core.Internal
}
finally
{
cancellationTokenSource.Cancel();
timeoutCts.Cancel();
}
}
}

+ 24
- 13
Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj View File

@@ -1,54 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.3;net451;uap10.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks>
<AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace>
<AssemblyVersion>2.5.0.0</AssemblyVersion>
<FileVersion>2.5.0.0</FileVersion>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugType>Full</DebugType>
<AssemblyVersion>0.0.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
<Version>0.0.0.0</Version>
<Company />
<Product />
<Description />
<Authors />
<PackageId />
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.15063.0</TargetPlatformVersion>
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
<DefineConstants>RELEASE;NETSTANDARD1_3</DefineConstants>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.0" />
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net452'">
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net461'">
</ItemGroup>

</Project>

+ 19
- 0
Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs View File

@@ -0,0 +1,19 @@
using System;

namespace MQTTnet.ManagedClient
{
public class ApplicationMessageProcessedEventArgs : EventArgs
{
public ApplicationMessageProcessedEventArgs(MqttApplicationMessage applicationMessage, Exception exception)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
Exception = exception;
}

public MqttApplicationMessage ApplicationMessage { get; }
public Exception Exception { get; }

public bool HasFailed => Exception != null;
public bool HasSucceeded => Exception == null;
}
}

MQTTnet.Core/ManagedClient/IManagedMqttClient.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs View File

@@ -1,21 +1,24 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Core.Client;
using MQTTnet.Client;

namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{
bool IsStarted { get; }
bool IsConnected { get; }

event EventHandler<MqttClientConnectedEventArgs> Connected;
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;

event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;

Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync();

Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topics);
}
}

MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs View File

@@ -1,7 +1,7 @@
using System;
using MQTTnet.Core.Client;
using MQTTnet.Client;

namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClientOptions
{

MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClientStorage
{

MQTTnet.Core/ManagedClient/ManagedMqttClient.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs View File

@@ -4,29 +4,32 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol;
using Microsoft.Extensions.Logging;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;

namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClient : IManagedMqttClient
{
private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager();
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>();
private readonly HashSet<TopicFilter> _subscriptions = new HashSet<TopicFilter>();
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1);
private readonly List<string> _unsubscriptions = new List<string>();

private readonly IMqttClient _mqttClient;
private readonly ILogger<ManagedMqttClient> _logger;
private readonly IMqttNetLogger _logger;

private CancellationTokenSource _connectionCancellationToken;
private CancellationTokenSource _publishingCancellationToken;

private ManagedMqttClientStorageManager _storageManager;
private IManagedMqttClientOptions _options;

private bool _subscriptionsNotPushed;
public ManagedMqttClient(ILogger<ManagedMqttClient> logger, IMqttClient mqttClient)
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -37,10 +40,13 @@ namespace MQTTnet.Core.ManagedClient
}

public bool IsConnected => _mqttClient.IsConnected;
public bool IsStarted => _connectionCancellationToken != null;

public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
public event EventHandler SynchronizingSubscriptionsFailed;

public async Task StartAsync(IManagedMqttClientOptions options)
{
@@ -52,36 +58,29 @@ namespace MQTTnet.Core.ManagedClient
throw new NotSupportedException("The managed client does not support existing sessions.");
}

if (_connectionCancellationToken != null)
{
throw new InvalidOperationException("The managed client is already started.");
}
if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started.");

_options = options;
await _storageManager.SetStorageAsync(_options.Storage).ConfigureAwait(false);

if (_options.Storage != null)
{
var loadedMessages = await _options.Storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
foreach (var loadedMessage in loadedMessages)
{
_messageQueue.Add(loadedMessage);
}
_storageManager = new ManagedMqttClientStorageManager(_options.Storage);
await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false);
}

_connectionCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Factory.StartNew(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

_logger.LogInformation("Started");
_logger.Info<ManagedMqttClient>("Started");
}

public Task StopAsync()
{
_connectionCancellationToken?.Cancel(false);
_connectionCancellationToken = null;
StopPublishing();
StopMaintainingConnection();

while (_messageQueue.Any())
{
@@ -97,43 +96,60 @@ namespace MQTTnet.Core.ManagedClient

foreach (var applicationMessage in applicationMessages)
{
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
if (_storageManager != null)
{
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
}

_messageQueue.Add(applicationMessage);
}
}

public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));

lock (_subscriptions)
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
try
{
foreach (var topicFilter in topicFilters)
{
if (_subscriptions.Add(topicFilter))
{
_subscriptionsNotPushed = true;
}
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
_subscriptionsNotPushed = true;
}
}

return Task.FromResult(0);
finally
{
_subscriptionsSemaphore.Release();
}
}

public Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters)
public async Task UnsubscribeAsync(IEnumerable<string> topics)
{
lock (_subscriptions)
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
try
{
foreach (var topicFilter in topicFilters)
foreach (var topic in topics)
{
if (_subscriptions.Remove(topicFilter))
if (_subscriptions.Remove(topic))
{
_unsubscriptions.Add(topic);
_subscriptionsNotPushed = true;
}
}
}
finally
{
_subscriptionsSemaphore.Release();
}
}

return Task.FromResult(0);
public void Dispose()
{
_messageQueue?.Dispose();
_subscriptionsSemaphore?.Dispose();
_connectionCancellationToken?.Dispose();
_publishingCancellationToken?.Dispose();
}

private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
@@ -142,33 +158,48 @@ namespace MQTTnet.Core.ManagedClient
{
while (!cancellationToken.IsCancellationRequested)
{
var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false);
if (connectionState == ReconnectionResult.NotConnected)
{
_publishingCancellationToken?.Cancel(false);
_publishingCancellationToken = null;
await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
}
finally
{
await _mqttClient.DisconnectAsync().ConfigureAwait(false);
_logger.Info<ManagedMqttClient>("Stopped");
}
}

await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
continue;
}
private async Task TryMaintainConnectionAsync(CancellationToken cancellationToken)
{
try
{
var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false);
if (connectionState == ReconnectionResult.NotConnected)
{
StopPublishing();
await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
return;
}

if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
{
await PushSubscriptionsAsync();
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
{
await SynchronizeSubscriptionsAsync().ConfigureAwait(false);

_publishingCancellationToken = new CancellationTokenSource();
StartPublishing();

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Factory.StartNew(() => PublishQueuedMessagesAsync(_publishingCancellationToken.Token), _publishingCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed

continue;
}
return;
}

if (connectionState == ReconnectionResult.StillConnected)
{
await Task.Delay(100, _connectionCancellationToken.Token).ConfigureAwait(false); // Consider using the _Disconnected_ event here. (TaskCompletionSource)
}
if (connectionState == ReconnectionResult.StillConnected)
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
@@ -176,16 +207,11 @@ namespace MQTTnet.Core.ManagedClient
}
catch (MqttCommunicationException exception)
{
_logger.LogWarning(new EventId(), exception, "Communication exception while maintaining connection.");
_logger.Warning<ManagedMqttClient>(exception, "Communication exception while maintaining connection.");
}
catch (Exception exception)
{
_logger.LogError(new EventId(), exception, "Unhandled exception while maintaining connection.");
}
finally
{
await _mqttClient.DisconnectAsync().ConfigureAwait(false);
_logger.LogInformation("Stopped");
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
}
}

@@ -207,27 +233,38 @@ namespace MQTTnet.Core.ManagedClient
}

await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
await _storageManager.RemoveAsync(message).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application messages.");
}
finally
{
_logger.LogInformation("Stopped publishing messages");
_logger.Verbose<ManagedMqttClient>("Stopped publishing messages.");
}
}

private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
{
Exception transmitException = null;
try
{
await _mqttClient.PublishAsync(message).ConfigureAwait(false);

if (_storageManager != null)
{
await _storageManager.RemoveAsync(message).ConfigureAwait(false);
}
}
catch (MqttCommunicationException exception)
{
_logger.LogWarning(new EventId(), exception, "Publishing application message failed.");
transmitException = exception;

_logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed.");

if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
@@ -236,34 +273,60 @@ namespace MQTTnet.Core.ManagedClient
}
catch (Exception exception)
{
_logger.LogError(new EventId(), exception, "Unhandled exception while publishing queued application message.");
transmitException = exception;
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message.");
}
finally
{
ApplicationMessageProcessed?.Invoke(this, new ApplicationMessageProcessedEventArgs(message, transmitException));
}
}

private async Task PushSubscriptionsAsync()
private async Task SynchronizeSubscriptionsAsync()
{
_logger.LogInformation(nameof(ManagedMqttClient), "Synchronizing subscriptions");
_logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions");

List<TopicFilter> subscriptions;
lock (_subscriptions)
List<string> unsubscriptions;

await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
try
{
subscriptions = _subscriptions.ToList();
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();

unsubscriptions = new List<string>(_unsubscriptions);
_unsubscriptions.Clear();

_subscriptionsNotPushed = false;
}

if (!_subscriptions.Any())
finally
{
_subscriptionsSemaphore.Release();
}
if (!subscriptions.Any() && !unsubscriptions.Any())
{
return;
}

try
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
if (subscriptions.Any())
{
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
}

if (unsubscriptions.Any())
{
await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false);
}
}
catch (Exception exception)
{
_logger.LogWarning(new EventId(), exception, "Synchronizing subscriptions failed");
_logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed.");
_subscriptionsNotPushed = true;

SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty);
}
}

@@ -299,5 +362,35 @@ namespace MQTTnet.Core.ManagedClient
{
Connected?.Invoke(this, eventArgs);
}

private void StartPublishing()
{
if (_publishingCancellationToken != null)
{
StopPublishing();
}

var cts = new CancellationTokenSource();

_publishingCancellationToken = cts;

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}

private void StopPublishing()
{
_publishingCancellationToken?.Cancel(false);
_publishingCancellationToken?.Dispose();
_publishingCancellationToken = null;
}

private void StopMaintainingConnection()
{
_connectionCancellationToken?.Cancel(false);
_connectionCancellationToken?.Dispose();
_connectionCancellationToken = null;
}
}
}

+ 39
- 0
Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs View File

@@ -0,0 +1,39 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Protocol;

namespace MQTTnet.ManagedClient
{
public static class ManagedMqttClientExtensions
{
public static Task SubscribeAsync(this IManagedMqttClient managedClient, params TopicFilter[] topicFilters)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));

return managedClient.SubscribeAsync(topicFilters);
}

public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
if (topic == null) throw new ArgumentNullException(nameof(topic));

return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}

public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
if (topic == null) throw new ArgumentNullException(nameof(topic));

return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build());
}

public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters)
{
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));

return managedClient.UnsubscribeAsync(topicFilters);
}
}
}

MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs View File

@@ -1,7 +1,7 @@
using System;
using MQTTnet.Core.Client;
using MQTTnet.Client;

namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientOptions : IManagedMqttClientOptions
{

+ 74
- 0
Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs View File

@@ -0,0 +1,74 @@
using System;
using MQTTnet.Client;

namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientOptionsBuilder
{
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions();
private MqttClientOptionsBuilder _clientOptionsBuilder;

public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value)
{
_options.AutoReconnectDelay = value;
return this;
}

public ManagedMqttClientOptionsBuilder WithStorage(IManagedMqttClientStorage value)
{
_options.Storage = value;
return this;
}

public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value)
{
if (_clientOptionsBuilder != null)
{
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
}

_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value));

return this;
}

public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptionsBuilder builder)
{
if (_options.ClientOptions != null)
{
throw new InvalidOperationException("Cannot use client options builder and client options at the same time.");
}

_clientOptionsBuilder = builder;
return this;
}

public ManagedMqttClientOptionsBuilder WithClientOptions(Action<MqttClientOptionsBuilder> options)
{
if (options == null) throw new ArgumentNullException(nameof(options));

if (_clientOptionsBuilder == null)
{
_clientOptionsBuilder = new MqttClientOptionsBuilder();
}

options(_clientOptionsBuilder);
return this;
}

public ManagedMqttClientOptions Build()
{
if (_clientOptionsBuilder != null)
{
_options.ClientOptions = _clientOptionsBuilder.Build();
}

if (_options.ClientOptions == null)
{
throw new InvalidOperationException("The ClientOptions cannot be null.");
}

return _options;
}
}
}

MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs View File

@@ -1,25 +1,27 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientStorageManager
{
private readonly List<MqttApplicationMessage> _applicationMessages = new List<MqttApplicationMessage>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private IManagedMqttClientStorage _storage;
private readonly IManagedMqttClientStorage _storage;

public async Task SetStorageAsync(IManagedMqttClientStorage storage)
public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
_storage = storage;
}
finally
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
}

public async Task LoadQueuedMessagesAsync()
{
var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
foreach (var loadedMessage in loadedMessages)
{
_semaphore.Release();
_applicationMessages.Add(loadedMessage);
}
}

@@ -28,11 +30,6 @@ namespace MQTTnet.Core.ManagedClient
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_storage == null)
{
return;
}

_applicationMessages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false);
}
@@ -47,11 +44,6 @@ namespace MQTTnet.Core.ManagedClient
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_storage == null)
{
return;
}

var index = _applicationMessages.IndexOf(applicationMessage);
if (index == -1)
{

MQTTnet.Core/ManagedClient/ReconnectionResult.cs → Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.ManagedClient
namespace MQTTnet.ManagedClient
{
public enum ReconnectionResult
{

MQTTnet.Core/MqttApplicationMessage.cs → Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs View File

@@ -1,7 +1,7 @@
using System;
using MQTTnet.Core.Protocol;
using MQTTnet.Protocol;

namespace MQTTnet.Core
namespace MQTTnet
{
public sealed class MqttApplicationMessage
{

MQTTnet.Core/MqttApplicationMessageBuilder.cs → Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs View File

@@ -2,10 +2,10 @@
using System.IO;
using System.Linq;
using System.Text;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;

namespace MQTTnet.Core
namespace MQTTnet
{
public class MqttApplicationMessageBuilder
{

+ 25
- 0
Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs View File

@@ -0,0 +1,25 @@
using System;
using System.Text;

namespace MQTTnet
{
public static class MqttApplicationMessageExtensions
{
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));

if (applicationMessage.Payload == null)
{
return null;
}

if (applicationMessage.Payload.Length == 0)
{
return string.Empty;
}

return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
}
}
}

MQTTnet.Core/MqttApplicationMessageReceivedEventArgs.cs → Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs View File

@@ -1,6 +1,6 @@
using System;

namespace MQTTnet.Core
namespace MQTTnet
{
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
{

+ 27
- 98
Frameworks/MQTTnet.NetStandard/MqttFactory.cs View File

@@ -1,130 +1,59 @@
using System;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Core.ManagedClient;
using MQTTnet.Core.Server;
using MQTTnet.Core.Channel;
using MQTTnet.ManagedClient;
using MQTTnet.Server;

namespace MQTTnet
{
public class MqttFactory : IMqttCommunicationAdapterFactory, IMqttClientSesssionFactory, IMqttClientFactory, IMqttServerFactory
public class MqttFactory : IMqttClientFactory, IMqttServerFactory
{
private readonly IServiceProvider _serviceProvider;

private static IServiceProvider BuildServiceProvider()
{
var serviceProvider = new ServiceCollection()
.AddMqttClient()
.AddMqttServer()
.AddLogging()
.BuildServiceProvider();

serviceProvider.GetRequiredService<ILoggerFactory>()
.AddMqttTrace();

return serviceProvider;
}

public MqttFactory()
: this(BuildServiceProvider())
{
}

public MqttFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public ILoggerFactory GetLoggerFactory()
{
return _serviceProvider.GetRequiredService<ILoggerFactory>();
}

public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options)
{
var logger = _serviceProvider.GetRequiredService<ILogger<MqttChannelCommunicationAdapter>>();
return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options.ChannelOptions), CreateSerializer(options.ProtocolVersion), logger);
}

public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel)
public IMqttClient CreateMqttClient()
{
var serializer = _serviceProvider.GetRequiredService<IMqttPacketSerializer>();
var logger = _serviceProvider.GetRequiredService<ILogger<MqttChannelCommunicationAdapter>>();
return new MqttChannelCommunicationAdapter(channel, serializer, logger);
return CreateMqttClient(new MqttNetLogger());
}

public IMqttCommunicationChannel CreateMqttCommunicationChannel(IMqttClientChannelOptions options)
public IMqttClient CreateMqttClient(IMqttNetLogger logger)
{
if (options == null) throw new ArgumentNullException(nameof(options));

switch (options)
{
case MqttClientTcpOptions tcpOptions:
return CreateTcpChannel(tcpOptions);
case MqttClientWebSocketOptions webSocketOptions:
return CreateWebSocketChannel(webSocketOptions);
default:
throw new NotSupportedException();
}
}
if (logger == null) throw new ArgumentNullException(nameof(logger));

public MqttTcpChannel CreateTcpChannel(MqttClientTcpOptions tcpOptions)
{
return new MqttTcpChannel(tcpOptions);
return new MqttClient(new MqttClientAdapterFactory(), logger);
}

public MqttWebSocketChannel CreateWebSocketChannel(MqttClientWebSocketOptions webSocketOptions)
public IManagedMqttClient CreateManagedMqttClient()
{
return new MqttWebSocketChannel(webSocketOptions);
return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger());
}

public MqttPacketSerializer CreateSerializer(MqttProtocolVersion protocolVersion)
public IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger)
{
return new MqttPacketSerializer
{
ProtocolVersion = protocolVersion
};
}
if (logger == null) throw new ArgumentNullException(nameof(logger));

public MqttClientSession CreateClientSession(string clientId, MqttClientSessionsManager clientSessionsManager)
{
return new MqttClientSession(
clientId,
_serviceProvider.GetRequiredService<IOptions<MqttServerOptions>>(),
clientSessionsManager,
_serviceProvider.GetRequiredService<MqttClientSubscriptionsManager>(),
_serviceProvider.GetRequiredService<ILogger<MqttClientSession>>(),
_serviceProvider.GetRequiredService<ILogger<MqttClientPendingMessagesQueue>>());
return new ManagedMqttClient(CreateMqttClient(), logger);
}

public IMqttClient CreateMqttClient()
public IMqttServer CreateMqttServer()
{
return _serviceProvider.GetRequiredService<IMqttClient>();
var logger = new MqttNetLogger();
return CreateMqttServer(logger);
}

public IManagedMqttClient CreateManagedMqttClient()
public IMqttServer CreateMqttServer(IMqttNetLogger logger)
{
return _serviceProvider.GetRequiredService<IManagedMqttClient>();
}
if (logger == null) throw new ArgumentNullException(nameof(logger));

public IMqttServer CreateMqttServer()
{
return _serviceProvider.GetRequiredService<IMqttServer>();
return CreateMqttServer(new List<IMqttServerAdapter> { new MqttTcpServerAdapter(logger) }, logger);
}

public IMqttServer CreateMqttServer(Action<MqttServerOptions> configure)
public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
{
if (configure == null) throw new ArgumentNullException(nameof(configure));

var options = _serviceProvider.GetRequiredService<IOptions<MqttServerOptions>>();
configure(options.Value);
if (adapters == null) throw new ArgumentNullException(nameof(adapters));
if (logger == null) throw new ArgumentNullException(nameof(logger));

return _serviceProvider.GetRequiredService<IMqttServer>();
return new MqttServer(adapters, logger);
}
}
}

+ 7
- 0
Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs View File

@@ -0,0 +1,7 @@
namespace MQTTnet.Packets
{
public interface IMqttPacketWithIdentifier
{
ushort? PacketIdentifier { get; set; }
}
}

MQTTnet.Core/Packets/MqttBasePacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs View File

@@ -1,4 +1,4 @@
namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public abstract class MqttBasePacket
{

MQTTnet.Core/Packets/MqttBasePublishPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs View File

@@ -1,7 +1,7 @@
namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public ushort? PacketIdentifier { get; set; }
}
}

MQTTnet.Core/Packets/MqttConnAckPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs View File

@@ -1,6 +1,6 @@
using MQTTnet.Core.Protocol;
using MQTTnet.Protocol;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttConnAckPacket : MqttBasePacket
{
@@ -10,7 +10,7 @@ namespace MQTTnet.Core.Packets

public override string ToString()
{
return nameof(MqttConnAckPacket) + ": [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]";
return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]";
}
}
}

MQTTnet.Core/Packets/MqttConnectPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs View File

@@ -1,6 +1,6 @@
using MQTTnet.Core.Serializer;
using MQTTnet.Serializer;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttConnectPacket : MqttBasePacket
{
@@ -20,7 +20,7 @@ namespace MQTTnet.Core.Packets

public override string ToString()
{
return nameof(MqttConnectPacket) + ": [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]";
return "Connect: [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]";
}
}
}

+ 10
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs View File

@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttDisconnectPacket : MqttBasePacket
{
public override string ToString()
{
return "Disconnect";
}
}
}

MQTTnet.Core/Packets/MqttPacketHeader.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs View File

@@ -1,6 +1,6 @@
using MQTTnet.Core.Protocol;
using MQTTnet.Protocol;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public class MqttPacketHeader
{

MQTTnet.Core/Packets/MqttPingReqPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs View File

@@ -1,10 +1,10 @@
namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttPingReqPacket : MqttBasePacket
{
public override string ToString()
{
return nameof(MqttPingReqPacket);
return "PingReq";
}
}
}

MQTTnet.Core/Packets/MqttPingRespPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs View File

@@ -1,10 +1,10 @@
namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttPingRespPacket : MqttBasePacket
{
public override string ToString()
{
return nameof(MqttPingRespPacket);
return "PingResp";
}
}
}

+ 10
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs View File

@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubAckPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubAck";
}
}
}

+ 10
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs View File

@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubCompPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubComp";
}
}
}

+ 10
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs View File

@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubRecPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubRec";
}
}
}

+ 10
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs View File

@@ -0,0 +1,10 @@
namespace MQTTnet.Packets
{
public sealed class MqttPubRelPacket : MqttBasePublishPacket
{
public override string ToString()
{
return "PubRel";
}
}
}

MQTTnet.Core/Packets/MqttPublishPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs View File

@@ -1,7 +1,6 @@
using System;
using MQTTnet.Core.Protocol;
using MQTTnet.Protocol;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttPublishPacket : MqttBasePublishPacket
{
@@ -17,9 +16,8 @@ namespace MQTTnet.Core.Packets

public override string ToString()
{
return nameof(MqttPublishPacket) +
": [Topic=" + Topic + "]" +
" [Payload=" + Convert.ToBase64String(Payload) + "]" +
return "Publish: [Topic=" + Topic + "]" +
" [Payload.Length=" + Payload?.Length + "]" +
" [QoSLevel=" + QualityOfServiceLevel + "]" +
" [Dup=" + Dup + "]" +
" [Retain=" + Retain + "]" +

MQTTnet.Core/Packets/MqttSubAckPacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs View File

@@ -1,19 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using MQTTnet.Core.Protocol;
using MQTTnet.Protocol;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public ushort? PacketIdentifier { get; set; }

public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>();

public override string ToString()
{
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString()));
return nameof(MqttSubAckPacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]";
return "SubAck: [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]";
}
}
}

MQTTnet.Core/Packets/MqttSubscribePacket.cs → Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs View File

@@ -1,18 +1,18 @@
using System.Collections.Generic;
using System.Linq;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Packets
{
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public ushort? PacketIdentifier { get; set; }

public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();

public override string ToString()
{
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel));
return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
}
}
}

+ 12
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs View File

@@ -0,0 +1,12 @@
namespace MQTTnet.Packets
{
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort? PacketIdentifier { get; set; }

public override string ToString()
{
return "UnsubAck: [PacketIdentifier=" + PacketIdentifier + "]";
}
}
}

+ 17
- 0
Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;

namespace MQTTnet.Packets
{
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort? PacketIdentifier { get; set; }

public IList<string> TopicFilters { get; set; } = new List<string>();

public override string ToString()
{
var topicFiltersText = string.Join(",", TopicFilters);
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
}
}
}

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

Loading…
Cancel
Save