Compare commits

..
49 Commits
Author SHA1 Message Date
Christian c5d22f4b27 Update docs 2018-03-13 20:23:36 +01:00
Christian e57ec11ed8 Update libs 2018-03-13 20:23:31 +01:00
Christian 56acc82445 Fix null ref in server when using interceptor 2018-03-13 20:23:22 +01:00
Christian 6e828d091f Override ToString() for LogMessage 2018-03-13 20:23:07 +01:00
Christian e1d8697331 Fix wrong parsing and generating of MqttConnAck for version 3.1.0 2018-03-13 20:22:48 +01:00
Christian 90098c2006 Optimize dispatcher 2018-03-13 20:22:13 +01:00
Christian b3a25fb9e8 Fix subscription handling of _ManagedClient_. 2018-03-05 20:43:37 +01:00
Christian f85ad4965b Refactor keep alive monitoring and add unit tests. 2018-02-28 21:43:19 +01:00
Christian f5696b6fc0 Merge branch 'develop' 2018-02-21 21:33:01 +01:00
Christian c26555aca5 Refactor ManagedClient 2018-02-21 21:32:21 +01:00
Christian 0accb467b5 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-02-21 20:44:10 +01:00
Christian 3b34fd4750 Fix build 2018-02-21 20:44:05 +01:00
Christian abb216c055 Fix retained message handling 2018-02-21 20:43:22 +01:00
Christian 1760dc7d35 Fix async issues 2018-02-21 20:43:09 +01:00
Christian 034e7fd750 Add event for processed application message (managed client only) 2018-02-21 20:42:39 +01:00
Christian 76c7b319b8 Fix build errors 2018-02-21 20:42:05 +01:00
Christian 09010e95a2 Merge pull request #180 from lerppana/managed_unsubscribe_fix
Fix ManagedMqttClient missing call to unsubscribe topics
2018-02-21 20:09:10 +01:00
Lassi Laati 2341d66192 Fix ManagedMqttClient missing call to unsubscribe topics 2018-02-20 09:23:47 +02:00
Christian 21586b7710 Merge branch 'master' of https://github.com/chkr1011/MQTTnet 2018-02-04 20:10:11 +01:00
Christian b426ae1198 Update docs 2018-02-04 20:06:01 +01:00
Christian e9db9d65ac Update README.md 2018-02-04 20:05:34 +01:00
Christian 59dcbf4c0c Add server events for subscribed and unsubscribed topics. Add pending messages count for connected clients. 2018-02-02 22:44:21 +01:00
Christian a204d233f6 Update configs 2018-02-02 00:47:00 +01:00
Christian 26cd84c88c Fix generation of packet identifier. 2018-02-02 00:46:47 +01:00
Christian 5f77255893 Update docs 2018-01-27 14:12:54 +01:00
Christian 8856a7aa34 Fix broken package reader 2018-01-26 23:56:22 +01:00
Christian 30754433eb Add server methods for controlling the client subscriptions. 2018-01-26 22:06:21 +01:00
Christian 751ac19c4b Fix reading more data than required for the body. 2018-01-26 21:25:56 +01:00
Christian 1415e4b878 Covnert the body buffer to a memory stream which will grow automatically. 2018-01-24 20:49:47 +01:00
Christian e56203faac Update docs 2018-01-24 20:16:47 +01:00
Christian 64c997a4a2 Merge pull request #163 from kpreisser/fixInfiniteLoop
Fix infinite loop when the client closes the connection
2018-01-24 20:12:15 +01:00
Christian 391ee34a0f Merge pull request #166 from kpreisser/fixDecodeRemainingLength
Fix the algorithm for decoding the body length
2018-01-24 20:09:14 +01:00
kpreisser 88de5047bc Fix the algorithm for decoding the remaining length to allow a length greater than 2,097,151 bytes.
See: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os.html#_Toc442180732
2018-01-24 15:32:36 +01:00
kpreisser d020e809a1 Fix infinite loop that can occur when the client closes the connection before sending the complete packet body. 2018-01-23 10:45:58 +01:00
Christian 8b2b8d41df Update docs 2018-01-22 21:11:44 +01:00
Christian 40c33ac639 Refactoring 2018-01-22 21:11:35 +01:00
Christian 002e4b8d69 Fix broken unit tests 2018-01-22 20:55:50 +01:00
Christian c060c379d3 Add thrown exception for disconnected event 2018-01-22 20:55:06 +01:00
Christian 4d13407a83 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2018-01-21 14:33:33 +01:00
Christian d737f550c7 Add encrypted code signing key 2018-01-21 14:33:29 +01:00
Christian 5e8d5b9219 Add documents 2018-01-21 14:29:43 +01:00
Christian f98f21ffdf Update libs 2018-01-21 14:29:36 +01:00
Christian ec9e64ebfb Update docs 2018-01-21 14:14:04 +01:00
Christian 39672970e2 Update 3rd-Partly libs 2018-01-21 14:13:50 +01:00
Christian 5becf7980a Merge pull request #157 from kpreisser/trueAsync
Use asynchronous methods for blocking operations
2018-01-21 14:12:29 +01:00
Christian c7c84ad39b Make clients interfaces disposable. 2018-01-21 14:10:23 +01:00
Christian 8773117493 Fix an issue which adds a delay after every accepted connection. 2018-01-21 14:09:34 +01:00
kpreisser bb697e1bd9 Follow-Up: Add missing ConfigureAwait() call. 2018-01-17 11:29:48 +01:00
kpreisser e1a9aa7d03 Adjust the MqttClientPendingMessagesQueue and the MqttPacketReader to use asynchronous methods for long-running/blocking operations.
Otherwise, Worker Threads from the .NET Threadpool are blocked while waiting for the next client message, and while waiting for the next message to send.
2018-01-17 11:10:39 +01:00
49 changed files with 1010 additions and 401 deletions
+4 -4
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet.AspNetCore</id> <id>MQTTnet.AspNetCore</id>
<version>2.6.0</version> <version>2.7.0</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,13 +10,13 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This is a support library to integrate MQTTnet into AspNetCore.</description> <description>This is a support library to integrate MQTTnet into AspNetCore.</description>
<releaseNotes>* Updated to MQTTnet 2.6.0. <releaseNotes>* Updated to MQTTnet 2.7.0.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.6.0" /> <dependency id="MQTTnet" version="2.7.0" />
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>
+11 -23
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.6.0</version> <version>2.7.1</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,28 +10,17 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes>* [Core] Merged projects (BREAKING CHANGE! But only namespace changes). <releaseNotes>* [Core] Fixed wrong parsing of ConnAck packet for protocol version 3.1.0.
* [Core] Added a strong name for the assembly. * [Core] Log messages are now overriding ToString() and providing a ready to use text representation.
* [Core] Performance optimizations. * [Client] Optimized package dispatcher and added several new exceptions.
* [Core] Fixed a logging issue when dealing with IOExceptions. * [Client] The _ManagedClient_ now has an event which is fired after a queued application message was processed (including exception).
* [Core] Fixed a typo in the global logger class (BREAKING CHANGE! Please find new example in Wiki). * [Client] The _ManagedClient_ now supports unsubscribing (thanks to @lerppana)
* [Core] Added support for project compilation under macOS (Thanks to @FourOnes). * [Server] Fixed some minor async issues.
* [Client] The certificate for encryption is now loaded as a _X509Certificate2_ which prodives more data (Thanks to @cvellan). * [Server] Fixed wrong comparison of the topic and QoS for retained messages.
* [Client] Fixed an issue in _ManagedClient_ which can cause the client to stop when publishing subscriptions. * [Server] Added a property which provides access to the used options (read only).
* [Client] Fixed an issue in _ManagedClient_ which prevents changing the QoS of an existing subscription (BREAKING CHANGE!). * [Server] Fixed a null ref expection when using an interceptor and publishing via the server directly.
* [Client] Fixed an issue in _MqttClientOptionsBuilder_ which prevents adding TLS options to the client options when calling _Build()_. (Thanks to @cvellan).
* [Client] Added an overload for subscribing at both clients which reduces required code.
* [Client] Added the _ClientId_ to the application message interceptor context.
* [Server] The application message interceptor can now delete any received application message.
* [Server] Added a ConnectionValidator context to align with other APIs (BREAKING CHANGE! Please find new example in Wiki).
* [Server] Added an interface for the _MqttServerOptions_.
* [Server] Added packet statistics for the connected clients.
* [Server] Fixed a security issue which sends retained packages to a failed subscription.
* [Server] Fixed the response (MaximumQoS) of a subscription (Thanks to @redbeans2017).
* [Server] The keep alive timeouts are now checked for every client (Thanks to @RainerMueller82).
* [Server] The used IP addresses can be specified now (IPAddress.Any as default).
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
@@ -75,7 +64,6 @@
<!-- .NET Framework --> <!-- .NET Framework -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/> <file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/> <file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/>
</files> </files>
+10 -8
View File
@@ -1,6 +1,7 @@
param([string]$assemblyVersion, [string]$nugetVersion) 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' $vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere'
@@ -9,14 +10,14 @@ if ($path) {
$msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe' $msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe'
# Build the core library # 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 &$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 &$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 &$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 &$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 &$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 # 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 &$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"
# Build the RPC extension # 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="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m
@@ -25,7 +26,8 @@ if ($path) {
&$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="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 &$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 Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path .\NuGet New-Item -ItemType Directory -Force -Path .\NuGet
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion .\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.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion
Binary file not shown.
+9
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
@@ -20,6 +20,7 @@
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
@@ -16,9 +16,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -17,8 +17,7 @@ namespace MQTTnet.Adapter
public class MqttChannelAdapter : IMqttChannelAdapter public class MqttChannelAdapter : IMqttChannelAdapter
{ {
private const uint ErrorOperationAborted = 0x800703E3; private const uint ErrorOperationAborted = 0x800703E3;
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
private static readonly byte[] EmptyBody = new byte[0];
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -91,28 +90,35 @@ namespace MQTTnet.Adapter
MqttBasePacket packet = null; MqttBasePacket packet = null;
await ExecuteAndWrapExceptionAsync(async () => await ExecuteAndWrapExceptionAsync(async () =>
{ {
ReceivedMqttPacket receivedMqttPacket; ReceivedMqttPacket receivedMqttPacket = null;
if (timeout > TimeSpan.Zero) try
{ {
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); if (timeout > TimeSpan.Zero)
} {
else receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
{ }
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false); else
} {
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
}
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
{ {
throw new TaskCanceledException(); throw new TaskCanceledException();
} }
packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body); packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body);
if (packet == null) if (packet == null)
{ {
throw new MqttProtocolViolationException("Received malformed packet."); throw new MqttProtocolViolationException("Received malformed packet.");
} }
_logger.Trace<MqttChannelAdapter>("RX <<< {0}", packet); _logger.Trace<MqttChannelAdapter>("RX <<< {0}", packet);
}
finally
{
receivedMqttPacket?.Dispose();
}
}).ConfigureAwait(false); }).ConfigureAwait(false);
return packet; return packet;
@@ -120,7 +126,7 @@ namespace MQTTnet.Adapter
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken) private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
{ {
var header = MqttPacketReader.ReadHeaderFromSource(stream, cancellationToken); var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
if (header == null) if (header == null)
{ {
return null; return null;
@@ -128,17 +134,34 @@ namespace MQTTnet.Adapter
if (header.BodyLength == 0) if (header.BodyLength == 0)
{ {
return new ReceivedMqttPacket(header, EmptyBody); return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false));
} }
var body = new byte[header.BodyLength]; var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream();
var offset = 0; var buffer = new byte[ReadBufferSize];
do while (body.Length < header.BodyLength)
{ {
var readBytesCount = await stream.ReadAsync(body, offset, body.Length - offset, cancellationToken).ConfigureAwait(false); var bytesLeft = header.BodyLength - (int)body.Length;
offset += readBytesCount; if (bytesLeft > buffer.Length)
} while (offset < header.BodyLength); {
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); return new ReceivedMqttPacket(header, body);
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.IO;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Adapter namespace MQTTnet.Adapter
{ {
public class ReceivedMqttPacket public sealed class ReceivedMqttPacket : IDisposable
{ {
public ReceivedMqttPacket(MqttPacketHeader header, byte[] body) public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body)
{ {
Header = header ?? throw new ArgumentNullException(nameof(header)); Header = header ?? throw new ArgumentNullException(nameof(header));
Body = body ?? throw new ArgumentNullException(nameof(body)); Body = body ?? throw new ArgumentNullException(nameof(body));
@@ -13,6 +14,11 @@ namespace MQTTnet.Adapter
public MqttPacketHeader Header { get; } public MqttPacketHeader Header { get; }
public byte[] Body { get; } public MemoryStream Body { get; }
public void Dispose()
{
Body?.Dispose();
}
} }
} }
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{ {
bool IsConnected { get; } bool IsConnected { get; }
@@ -12,15 +12,15 @@ using MQTTnet.Protocol;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public class MqttClient : IMqttClient, IDisposable public class MqttClient : IMqttClient
{ {
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher; private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private IMqttClientOptions _options; private IMqttClientOptions _options;
private bool _isReceivingPackets; private bool _isReceivingPackets;
private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
@@ -49,7 +49,7 @@ namespace MQTTnet.Client
{ {
_options = options; _options = options;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0; _packetIdentifierProvider.Reset();
_packetDispatcher.Reset(); _packetDispatcher.Reset();
_adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger); _adapter = _adapterFactory.CreateClientAdapter(options.ChannelOptions, _logger);
@@ -75,7 +75,8 @@ namespace MQTTnet.Client
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClient>(exception, "Error while connecting with server."); _logger.Error<MqttClient>(exception, "Error while connecting with server.");
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
throw; throw;
} }
} }
@@ -93,7 +94,7 @@ namespace MQTTnet.Client
} }
finally finally
{ {
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(null).ConfigureAwait(false);
} }
} }
@@ -105,7 +106,7 @@ namespace MQTTnet.Client
var subscribePacket = new MqttSubscribePacket var subscribePacket = new MqttSubscribePacket
{ {
PacketIdentifier = GetNewPacketIdentifier(), PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList() TopicFilters = topicFilters.ToList()
}; };
@@ -127,7 +128,7 @@ namespace MQTTnet.Client
var unsubscribePacket = new MqttUnsubscribePacket var unsubscribePacket = new MqttUnsubscribePacket
{ {
PacketIdentifier = GetNewPacketIdentifier(), PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList() TopicFilters = topicFilters.ToList()
}; };
@@ -155,7 +156,7 @@ namespace MQTTnet.Client
{ {
foreach (var publishPacket in qosGroup) foreach (var publishPacket in qosGroup)
{ {
publishPacket.PacketIdentifier = GetNewPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false); await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false);
} }
@@ -165,7 +166,7 @@ namespace MQTTnet.Client
{ {
foreach (var publishPacket in qosGroup) foreach (var publishPacket in qosGroup)
{ {
publishPacket.PacketIdentifier = GetNewPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false); var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false); await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false);
} }
@@ -216,7 +217,7 @@ namespace MQTTnet.Client
if (IsConnected) throw new MqttProtocolViolationException(message); if (IsConnected) throw new MqttProtocolViolationException(message);
} }
private async Task DisconnectInternalAsync() private async Task DisconnectInternalAsync(Exception exception)
{ {
var clientWasConnected = IsConnected; var clientWasConnected = IsConnected;
IsConnected = false; IsConnected = false;
@@ -236,14 +237,14 @@ namespace MQTTnet.Client
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
_logger.Info<MqttClient>("Disconnected from adapter."); _logger.Info<MqttClient>("Disconnected from adapter.");
} }
catch (Exception exception) catch (Exception adapterException)
{ {
_logger.Warning<MqttClient>(exception, "Error while disconnecting from adapter."); _logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter.");
} }
finally finally
{ {
_logger.Info<MqttClient>("Disconnected."); _logger.Info<MqttClient>("Disconnected.");
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected)); Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception));
} }
} }
@@ -334,16 +335,17 @@ namespace MQTTnet.Client
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{ {
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.CommunicationTimeout); ushort identifier = 0;
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
{
identifier = requestPacketWithIdentifier.PacketIdentifier;
}
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout);
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false); await _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
} }
private ushort GetNewPacketIdentifier()
{
return (ushort)Interlocked.Increment(ref _latestPacketIdentifier);
}
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{ {
_logger.Info<MqttClient>("Start sending keep alive packets."); _logger.Info<MqttClient>("Start sending keep alive packets.");
@@ -363,7 +365,7 @@ namespace MQTTnet.Client
return; return;
} }
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(null).ConfigureAwait(false);
} }
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
@@ -373,12 +375,12 @@ namespace MQTTnet.Client
} }
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets."); _logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets."); _logger.Warning<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
finally finally
{ {
@@ -413,7 +415,7 @@ namespace MQTTnet.Client
return; return;
} }
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(null).ConfigureAwait(false);
} }
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
@@ -423,12 +425,12 @@ namespace MQTTnet.Client
} }
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets."); _logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets."); _logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false); await DisconnectInternalAsync(exception).ConfigureAwait(false);
} }
finally finally
{ {
@@ -4,11 +4,14 @@ namespace MQTTnet.Client
{ {
public class MqttClientDisconnectedEventArgs : EventArgs public class MqttClientDisconnectedEventArgs : EventArgs
{ {
public MqttClientDisconnectedEventArgs(bool clientWasConnected) public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception)
{ {
ClientWasConnected = clientWasConnected; ClientWasConnected = clientWasConnected;
Exception = exception;
} }
public bool ClientWasConnected { get; } public bool ClientWasConnected { get; }
public Exception Exception { get; }
} }
} }
@@ -10,8 +10,7 @@ namespace MQTTnet.Client
{ {
public class MqttPacketDispatcher public class MqttPacketDispatcher
{ {
private readonly ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>> _packetByResponseType = new ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>>(); private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
public MqttPacketDispatcher(IMqttNetLogger logger) public MqttPacketDispatcher(IMqttNetLogger logger)
@@ -19,11 +18,9 @@ namespace MQTTnet.Client
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
public async Task<MqttBasePacket> WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout) public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort identifier, TimeSpan timeout)
{ {
if (request == null) throw new ArgumentNullException(nameof(request)); var packetAwaiter = AddPacketAwaiter(responseType, identifier);
var packetAwaiter = AddPacketAwaiter(request, responseType);
try try
{ {
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false); return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
@@ -35,7 +32,7 @@ namespace MQTTnet.Client
} }
finally finally
{ {
RemovePacketAwaiter(request, responseType); RemovePacketAwaiter(responseType, identifier);
} }
} }
@@ -44,21 +41,20 @@ namespace MQTTnet.Client
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
var type = packet.GetType(); var type = packet.GetType();
if (packet is IMqttPacketWithIdentifier withIdentifier)
if (_awaiters.TryGetValue(type, out var byId))
{ {
if (_packetByResponseTypeAndIdentifier.TryGetValue(type, out var byid)) ushort identifier = 0;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier)
{ {
if (byid.TryRemove(withIdentifier.PacketIdentifier, out var tcs)) identifier = packetWithIdentifier.PacketIdentifier;
{ }
tcs.TrySetResult(packet);
return; if (byId.TryRemove(identifier, out var tcs))
} {
tcs.TrySetResult(packet);
return;
} }
}
else if (_packetByResponseType.TryRemove(type, out var tcs))
{
tcs.TrySetResult(packet);
return;
} }
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched."); throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
@@ -66,38 +62,26 @@ namespace MQTTnet.Client
public void Reset() public void Reset()
{ {
_packetByResponseTypeAndIdentifier.Clear(); _awaiters.Clear();
_packetByResponseType.Clear();
} }
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(MqttBasePacket request, Type responseType) private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort identifier)
{ {
var tcs = new TaskCompletionSource<MqttBasePacket>(); var tcs = new TaskCompletionSource<MqttBasePacket>();
if (request is IMqttPacketWithIdentifier requestWithIdentifier) var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
if (!byId.TryAdd(identifier, tcs))
{ {
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}.");
byId[requestWithIdentifier.PacketIdentifier] = tcs;
}
else
{
_packetByResponseType[responseType] = tcs;
} }
return tcs; return tcs;
} }
private void RemovePacketAwaiter(MqttBasePacket request, Type responseType) private void RemovePacketAwaiter(Type responseType, ushort identifier)
{ {
if (request is IMqttPacketWithIdentifier requestWithIdentifier) var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
{ byId.TryRemove(identifier, out var _);
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
}
else
{
_packetByResponseType.TryRemove(responseType, out var _);
}
} }
} }
} }
@@ -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;
}
}
}
}
@@ -28,5 +28,16 @@ namespace MQTTnet.Diagnostics
public string Message { get; } public string Message { get; }
public Exception Exception { 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;
}
} }
} }
@@ -110,22 +110,14 @@ namespace MQTTnet.Implementations
{ {
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
} }
catch (SocketException exception) catch (Exception exception)
{ {
if (exception.SocketErrorCode == SocketError.OperationAborted) if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
{ {
return; return;
} }
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at default endpoint."); _logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at default endpoint.");
}
catch (Exception exception)
{
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at default endpoint.");
}
finally
{
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
} }
} }
@@ -153,22 +145,14 @@ namespace MQTTnet.Implementations
{ {
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
} }
catch (SocketException exception) catch (Exception exception)
{ {
if (exception.SocketErrorCode == SocketError.OperationAborted) if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted)
{ {
return; return;
} }
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at default endpoint.");
}
catch (Exception exception)
{
_logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at TLS endpoint."); _logger.Error<MqttServerAdapter>(exception, "Error while accepting connection at TLS endpoint.");
}
finally
{
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
} }
} }
@@ -30,7 +30,7 @@ namespace MQTTnet.Internal
if (task.IsFaulted) if (task.IsFaulted)
{ {
throw new MqttCommunicationException(task.Exception.GetBaseException()); throw new MqttCommunicationException(task.Exception?.GetBaseException());
} }
} }
finally finally
@@ -15,8 +15,7 @@
<Description /> <Description />
<Authors /> <Authors />
<PackageId /> <PackageId />
<SignAssembly>true</SignAssembly> <SignAssembly>false</SignAssembly>
<AssemblyOriginatorKeyFile>codeSigningKey.pfx</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
</PropertyGroup> </PropertyGroup>
@@ -29,6 +28,7 @@
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> <TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> <DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefaultLanguage>en</DefaultLanguage>
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
@@ -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;
}
}
@@ -5,13 +5,15 @@ using MQTTnet.Client;
namespace MQTTnet.ManagedClient namespace MQTTnet.ManagedClient
{ {
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{ {
bool IsConnected { get; } bool IsConnected { get; }
event EventHandler<MqttClientConnectedEventArgs> Connected; event EventHandler<MqttClientConnectedEventArgs> Connected;
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
Task StartAsync(IManagedMqttClientOptions options); Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync(); Task StopAsync();
@@ -11,11 +11,12 @@ using MQTTnet.Protocol;
namespace MQTTnet.ManagedClient namespace MQTTnet.ManagedClient
{ {
public class ManagedMqttClient : IManagedMqttClient, IDisposable public class ManagedMqttClient : IManagedMqttClient
{ {
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>(); private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>();
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1);
private readonly List<string> _unsubscriptions = new List<string>();
private readonly IMqttClient _mqttClient; private readonly IMqttClient _mqttClient;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -27,7 +28,7 @@ namespace MQTTnet.ManagedClient
private IManagedMqttClientOptions _options; private IManagedMqttClientOptions _options;
private bool _subscriptionsNotPushed; private bool _subscriptionsNotPushed;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -43,6 +44,8 @@ namespace MQTTnet.ManagedClient
public event EventHandler<MqttClientConnectedEventArgs> Connected; public event EventHandler<MqttClientConnectedEventArgs> Connected;
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed;
public event EventHandler SynchronizingSubscriptionsFailed;
public async Task StartAsync(IManagedMqttClientOptions options) public async Task StartAsync(IManagedMqttClientOptions options)
{ {
@@ -57,7 +60,7 @@ namespace MQTTnet.ManagedClient
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; _options = options;
if (_options.Storage != null) if (_options.Storage != null)
{ {
_storageManager = new ManagedMqttClientStorageManager(_options.Storage); _storageManager = new ManagedMqttClientStorageManager(_options.Storage);
@@ -65,9 +68,9 @@ namespace MQTTnet.ManagedClient
} }
_connectionCancellationToken = new CancellationTokenSource(); _connectionCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #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 MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token).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 #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
_logger.Info<ManagedMqttClient>("Started"); _logger.Info<ManagedMqttClient>("Started");
@@ -85,7 +88,7 @@ namespace MQTTnet.ManagedClient
return Task.FromResult(0); return Task.FromResult(0);
} }
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages) public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{ {
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
@@ -96,7 +99,7 @@ namespace MQTTnet.ManagedClient
{ {
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false);
} }
_messageQueue.Add(applicationMessage); _messageQueue.Add(applicationMessage);
} }
} }
@@ -129,6 +132,7 @@ namespace MQTTnet.ManagedClient
{ {
if (_subscriptions.Remove(topic)) if (_subscriptions.Remove(topic))
{ {
_unsubscriptions.Add(topic);
_subscriptionsNotPushed = true; _subscriptionsNotPushed = true;
} }
} }
@@ -153,7 +157,7 @@ namespace MQTTnet.ManagedClient
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await TryMaintainConnectionAsync(cancellationToken); await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -184,11 +188,11 @@ namespace MQTTnet.ManagedClient
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
{ {
await PushSubscriptionsAsync().ConfigureAwait(false); await SynchronizeSubscriptionsAsync().ConfigureAwait(false);
StartPublishing(); StartPublishing();
return; return;
} }
@@ -209,7 +213,7 @@ namespace MQTTnet.ManagedClient
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection."); _logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection.");
} }
} }
private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken) private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
{ {
try try
@@ -227,7 +231,7 @@ namespace MQTTnet.ManagedClient
continue; continue;
} }
await TryPublishQueuedMessageAsync(message).ConfigureAwait(false); await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -245,6 +249,7 @@ namespace MQTTnet.ManagedClient
private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message) private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
{ {
Exception transmitException = null;
try try
{ {
await _mqttClient.PublishAsync(message).ConfigureAwait(false); await _mqttClient.PublishAsync(message).ConfigureAwait(false);
@@ -256,6 +261,8 @@ namespace MQTTnet.ManagedClient
} }
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
transmitException = exception;
_logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed."); _logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed.");
if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
@@ -265,39 +272,60 @@ namespace MQTTnet.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
transmitException = exception;
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message."); _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.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions"); _logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions");
List<TopicFilter> subscriptions; List<TopicFilter> subscriptions;
List<string> unsubscriptions;
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
try try
{ {
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
unsubscriptions = new List<string>(_unsubscriptions);
_unsubscriptions.Clear();
_subscriptionsNotPushed = false; _subscriptionsNotPushed = false;
} }
finally finally
{ {
_subscriptionsSemaphore.Release(); _subscriptionsSemaphore.Release();
} }
if (!subscriptions.Any()) if (!subscriptions.Any() && !unsubscriptions.Any())
{ {
return; return;
} }
try 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) catch (Exception exception)
{ {
_logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed"); _logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed.");
_subscriptionsNotPushed = true; _subscriptionsNotPushed = true;
SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty);
} }
} }
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Serializer namespace MQTTnet.Serializer
@@ -10,6 +11,6 @@ namespace MQTTnet.Serializer
ICollection<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket); ICollection<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket);
MqttBasePacket Deserialize(MqttPacketHeader header, byte[] body); MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body);
} }
} }
@@ -22,17 +22,21 @@ namespace MQTTnet.Serializer
public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength; public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength;
public static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken) public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
{ {
var buffer = stream.ReadByte(); // Wait for the next package which starts with the header. At this point there will probably
if (buffer == -1) // some large delay and thus the thread should be put back to the pool (await). So ReadByte()
// is not an option here.
var buffer = new byte[1];
var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
if (readCount <= 0)
{ {
return null; return null;
} }
var fixedHeader = (byte)buffer; var fixedHeader = buffer[0];
var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4); var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
var bodyLength = ReadBodyLengthFromSource(stream, cancellationToken); var bodyLength = await ReadBodyLengthAsync(stream, cancellationToken).ConfigureAwait(false);
return new MqttPacketHeader return new MqttPacketHeader
{ {
@@ -80,13 +84,14 @@ namespace MQTTnet.Serializer
return ReadBytes(_header.BodyLength - (int)BaseStream.Position); return ReadBytes(_header.BodyLength - (int)BaseStream.Position);
} }
private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken) private static async Task<int> ReadBodyLengthAsync(Stream stream, CancellationToken cancellationToken)
{ {
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. // Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1; var multiplier = 1;
var value = 0; var value = 0;
byte encodedByte; byte encodedByte;
var buffer = new byte[1];
var readBytes = new List<byte>(); var readBytes = new List<byte>();
do do
{ {
@@ -95,21 +100,22 @@ namespace MQTTnet.Serializer
throw new TaskCanceledException(); throw new TaskCanceledException();
} }
var buffer = stream.ReadByte(); var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
if (buffer == -1) if (readCount <= 0)
{ {
throw new MqttCommunicationException("Connection closed while reading remaining length data."); throw new MqttCommunicationException("Connection closed while reading remaining length data.");
} }
encodedByte = (byte)buffer; encodedByte = buffer[0];
readBytes.Add(encodedByte); readBytes.Add(encodedByte);
value += (byte)(encodedByte & 127) * multiplier; value += (byte)(encodedByte & 127) * multiplier;
multiplier *= 128;
if (multiplier > 128 * 128 * 128) if (multiplier > 128 * 128 * 128)
{ {
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)})."); throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)}).");
} }
multiplier *= 128;
} while ((encodedByte & 128) != 0); } while ((encodedByte & 128) != 0);
return value; return value;
@@ -42,13 +42,12 @@ namespace MQTTnet.Serializer
} }
} }
public MqttBasePacket Deserialize(MqttPacketHeader header, byte[] body) public MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body)
{ {
if (header == null) throw new ArgumentNullException(nameof(header)); if (header == null) throw new ArgumentNullException(nameof(header));
if (body == null) throw new ArgumentNullException(nameof(body)); if (body == null) throw new ArgumentNullException(nameof(body));
using (var bodyStream = new MemoryStream(body)) using (var reader = new MqttPacketReader(header, body))
using (var reader = new MqttPacketReader(header, bodyStream))
{ {
return Deserialize(header, reader); return Deserialize(header, reader);
} }
@@ -76,7 +75,7 @@ namespace MQTTnet.Serializer
} }
} }
private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader) private MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
{ {
switch (header.ControlPacketType) switch (header.ControlPacketType)
{ {
@@ -279,20 +278,22 @@ namespace MQTTnet.Serializer
return packet; return packet;
} }
private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader) private MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
{ {
var variableHeader1 = reader.ReadByte(); var packet = new MqttConnAckPacket();
var variableHeader2 = reader.ReadByte();
var packet = new MqttConnAckPacket var firstByteReader = new ByteReader(reader.ReadByte());
if (ProtocolVersion == MqttProtocolVersion.V311)
{ {
IsSessionPresent = new ByteReader(variableHeader1).Read(), packet.IsSessionPresent = firstByteReader.Read();
ConnectReturnCode = (MqttConnectReturnCode)variableHeader2 }
};
packet.ConnectReturnCode = (MqttConnectReturnCode)reader.ReadByte();
return packet; return packet;
} }
private static void ValidateConnectPacket(MqttConnectPacket packet) private static void ValidateConnectPacket(MqttConnectPacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
@@ -374,14 +375,21 @@ namespace MQTTnet.Serializer
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer) private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer)
{ {
var connectAcknowledgeFlags = new ByteWriter(); if (ProtocolVersion == MqttProtocolVersion.V310)
if (ProtocolVersion == MqttProtocolVersion.V311)
{ {
writer.Write(0);
}
else if (ProtocolVersion == MqttProtocolVersion.V311)
{
var connectAcknowledgeFlags = new ByteWriter();
connectAcknowledgeFlags.Write(packet.IsSessionPresent); connectAcknowledgeFlags.Write(packet.IsSessionPresent);
writer.Write(connectAcknowledgeFlags);
}
else
{
throw new MqttProtocolViolationException("Protocol version not supported.");
} }
writer.Write(connectAcknowledgeFlags);
writer.Write((byte)packet.ConnectReturnCode); writer.Write((byte)packet.ConnectReturnCode);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
@@ -12,5 +12,7 @@ namespace MQTTnet.Server
public TimeSpan LastPacketReceived { get; set; } public TimeSpan LastPacketReceived { get; set; }
public TimeSpan LastNonKeepAlivePacketReceived { get; set; } public TimeSpan LastNonKeepAlivePacketReceived { get; set; }
public int PendingApplicationMessages { get; set; }
} }
} }
@@ -6,12 +6,20 @@ namespace MQTTnet.Server
{ {
public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher
{ {
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
event EventHandler<MqttServerStartedEventArgs> Started; event EventHandler<MqttServerStartedEventArgs> Started;
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
event EventHandler<MqttClientSubscribedTopicEventArgs> ClientSubscribedTopic;
event EventHandler<MqttClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopic;
IMqttServerOptions Options { get; }
Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync(); Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync();
Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters);
Task UnsubscribeAsync(string clientId, IList<string> topicFilters);
Task StartAsync(IMqttServerOptions options); Task StartAsync(IMqttServerOptions options);
Task StopAsync(); Task StopAsync();
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.Net;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
@@ -0,0 +1,88 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class MqttClientKeepAliveMonitor
{
private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
private readonly string _clientId;
private readonly Func<Task> _timeoutCallback;
private readonly IMqttNetLogger _logger;
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger)
{
_clientId = clientId;
_timeoutCallback = timeoutCallback;
_logger = logger;
}
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
{
if (keepAlivePeriod == 0)
{
return;
}
Task.Run(async () => await RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
}
private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
{
try
{
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
{
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
if (_timeoutCallback != null)
{
await _timeoutCallback().ConfigureAwait(false);
}
return;
}
await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
}
finally
{
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", _clientId);
}
}
public void PacketReceived(MqttBasePacket packet)
{
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
}
}
}
@@ -12,18 +12,21 @@ namespace MQTTnet.Server
{ {
public sealed class MqttClientPendingMessagesQueue : IDisposable public sealed class MqttClientPendingMessagesQueue : IDisposable
{ {
private readonly BlockingCollection<MqttBasePacket> _queue = new BlockingCollection<MqttBasePacket>(); private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0);
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly MqttClientSession _session; private readonly MqttClientSession _clientSession;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession session, IMqttNetLogger logger) public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_session = session ?? throw new ArgumentNullException(nameof(session));
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
public int Count => _queue.Count;
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken) public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); if (adapter == null) throw new ArgumentNullException(nameof(adapter));
@@ -40,8 +43,10 @@ namespace MQTTnet.Server
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
_queue.Add(packet); _queue.Enqueue(packet);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _session.ClientId); _queueWaitSemaphore.Release();
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
} }
private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
@@ -58,7 +63,7 @@ namespace MQTTnet.Server
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _session.ClientId); _logger.Error<MqttClientPendingMessagesQueue>(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId);
} }
} }
@@ -67,27 +72,32 @@ namespace MQTTnet.Server
MqttBasePacket packet = null; MqttBasePacket packet = null;
try try
{ {
packet = _queue.Take(cancellationToken); await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
if (!_queue.TryDequeue(out packet))
{
throw new InvalidOperationException(); // should not happen
}
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false);
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _session.ClientId); _logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
} }
catch (Exception exception) catch (Exception exception)
{ {
if (exception is MqttCommunicationTimedOutException) if (exception is MqttCommunicationTimedOutException)
{ {
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _session.ClientId); _logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _clientSession.ClientId);
} }
else if (exception is MqttCommunicationException) else if (exception is MqttCommunicationException)
{ {
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _session.ClientId); _logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _clientSession.ClientId);
} }
else if (exception is OperationCanceledException) else if (exception is OperationCanceledException)
{ {
} }
else else
{ {
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _session.ClientId); _logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId);
} }
if (packet is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
@@ -95,17 +105,18 @@ namespace MQTTnet.Server
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{ {
publishPacket.Dup = true; publishPacket.Dup = true;
_queue.Add(packet, CancellationToken.None); _queue.Enqueue(packet);
_queueWaitSemaphore.Release();
} }
} }
await _session.StopAsync(); await _clientSession.StopAsync();
} }
} }
public void Dispose() public void Dispose()
{ {
_queue?.Dispose(); _queueWaitSemaphore?.Dispose();
} }
} }
} }
@@ -1,5 +1,5 @@
using System; using System;
using System.Diagnostics; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -14,16 +14,9 @@ namespace MQTTnet.Server
{ {
public sealed class MqttClientSession : IDisposable public sealed class MqttClientSession : IDisposable
{ {
private readonly Stopwatch _lastPacketReceivedTracker = Stopwatch.StartNew();
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = Stopwatch.StartNew();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private readonly MqttClientSessionsManager _sessionsManager;
private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
private IMqttChannelAdapter _adapter; private IMqttChannelAdapter _adapter;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
@@ -33,28 +26,31 @@ namespace MQTTnet.Server
string clientId, string clientId,
IMqttServerOptions options, IMqttServerOptions options,
MqttRetainedMessagesManager retainedMessagesManager, MqttRetainedMessagesManager retainedMessagesManager,
MqttClientSessionsManager sessionsManager,
IMqttNetLogger logger) IMqttNetLogger logger)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
ClientId = clientId; ClientId = clientId;
_subscriptionsManager = new MqttClientSubscriptionsManager(_options); KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId);
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
} }
public Func<MqttClientSession, MqttApplicationMessage, Task> ApplicationMessageReceivedCallback { get; set; }
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
public MqttClientKeepAliveMonitor KeepAliveMonitor { get; }
public string ClientId { get; } public string ClientId { get; }
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion; public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
public bool IsConnected => _adapter != null; public bool IsConnected => _adapter != null;
public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
@@ -70,15 +66,8 @@ namespace MQTTnet.Server
_adapter = adapter; _adapter = adapter;
_cancellationTokenSource = cancellationTokenSource; _cancellationTokenSource = cancellationTokenSource;
_pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
_lastPacketReceivedTracker.Restart();
_lastNonKeepAlivePacketReceivedTracker.Restart();
if (connectPacket.KeepAlivePeriod > 0)
{
StartCheckingKeepAliveTimeout(TimeSpan.FromSeconds(connectPacket.KeepAlivePeriod), cancellationTokenSource.Token);
}
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false); await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
} }
@@ -121,8 +110,8 @@ namespace MQTTnet.Server
var willMessage = _willMessage; var willMessage = _willMessage;
if (willMessage != null) if (willMessage != null)
{ {
_willMessage = null; //clear willmessage so it is send just once _willMessage = null; // clear willmessage so it is send just once
await _sessionsManager.DispatchApplicationMessageAsync(this, willMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
} }
} }
} }
@@ -131,7 +120,7 @@ namespace MQTTnet.Server
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
var result = await _subscriptionsManager.CheckSubscriptionsAsync(applicationMessage); var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage);
if (!result.IsSubscribed) if (!result.IsSubscribed)
{ {
return; return;
@@ -140,15 +129,47 @@ namespace MQTTnet.Server
var publishPacket = applicationMessage.ToPublishPacket(); var publishPacket = applicationMessage.ToPublishPacket();
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel; publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
_pendingMessagesQueue.Enqueue(publishPacket); PendingMessagesQueue.Enqueue(publishPacket);
}
public async Task SubscribeAsync(IList<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket
{
TopicFilters = topicFilters
}).ConfigureAwait(false);
await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
}
public Task UnsubscribeAsync(IList<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket
{
TopicFilters = topicFilters
});
} }
public void Dispose() public void Dispose()
{ {
_pendingMessagesQueue?.Dispose(); ApplicationMessageReceivedCallback = null;
SubscriptionsManager?.Dispose();
PendingMessagesQueue?.Dispose();
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
} }
private Task StopDueToKeepAliveTimeoutAsync()
{
_logger.Info<MqttClientSession>("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
return StopAsync();
}
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
{ {
try try
@@ -156,14 +177,7 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false); var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
KeepAliveMonitor.PacketReceived(packet);
_lastPacketReceivedTracker.Restart();
if (!(packet is MqttPingReqPacket))
{
_lastNonKeepAlivePacketReceivedTracker.Restart();
}
await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false); await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
} }
} }
@@ -231,7 +245,7 @@ namespace MQTTnet.Server
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{ {
var subscribeResult = await _subscriptionsManager.SubscribeAsync(subscribePacket, ClientId); var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false);
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
@@ -240,22 +254,21 @@ namespace MQTTnet.Server
await StopAsync().ConfigureAwait(false); await StopAsync().ConfigureAwait(false);
} }
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket).ConfigureAwait(false); await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
} }
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{ {
var unsubscribeResult = await _subscriptionsManager.UnsubscribeAsync(unsubscribePacket); var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, unsubscribeResult);
} }
private async Task EnqueueSubscribedRetainedMessagesAsync(MqttSubscribePacket subscribePacket) private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
{ {
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(subscribePacket); var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
foreach (var publishPacket in retainedMessages) foreach (var applicationMessage in retainedMessages)
{ {
await EnqueueApplicationMessageAsync(publishPacket); await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
} }
} }
@@ -267,7 +280,7 @@ namespace MQTTnet.Server
{ {
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
return _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage); return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage);
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
{ {
@@ -286,7 +299,7 @@ namespace MQTTnet.Server
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false);
@@ -295,7 +308,7 @@ namespace MQTTnet.Server
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage).ConfigureAwait(false); await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response).ConfigureAwait(false);
@@ -306,42 +319,5 @@ namespace MQTTnet.Server
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, response);
} }
private void StartCheckingKeepAliveTimeout(TimeSpan keepAlivePeriod, CancellationToken cancellationToken)
{
Task.Run(
async () => await CheckKeepAliveTimeoutAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false)
, cancellationToken);
}
private async Task CheckKeepAliveTimeoutAsync(TimeSpan keepAlivePeriod, CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod.TotalSeconds * 1.5D)
{
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", ClientId);
await StopAsync();
return;
}
await Task.Delay(keepAlivePeriod, cancellationToken);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", ClientId);
}
finally
{
_logger.Trace<MqttClientSession>("Client {0}: Stopped checking keep alive timeout.", ClientId);
}
}
} }
} }
@@ -12,27 +12,32 @@ using MQTTnet.Serializer;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttClientSessionsManager public sealed class MqttClientSessionsManager : IDisposable
{ {
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>(); private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly MqttServer _server;
private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, MqttServer server, IMqttNetLogger logger) public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
{ {
_server = server ?? throw new ArgumentNullException(nameof(server));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
} }
public async Task RunClientSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; }
public Action<ConnectedMqttClient> ClientDisconnectedCallback { get; set; }
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; }
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; }
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; }
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
{ {
var clientId = string.Empty; var clientId = string.Empty;
MqttClientSession clientSession = null;
try try
{ {
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket)) if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket))
@@ -56,21 +61,22 @@ namespace MQTTnet.Server
return; return;
} }
var clientSession = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false);
clientSession = result.Session;
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
{ {
ConnectReturnCode = connectReturnCode, ConnectReturnCode = connectReturnCode,
IsSessionPresent = clientSession.IsExistingSession IsSessionPresent = result.IsExistingSession
}).ConfigureAwait(false); }).ConfigureAwait(false);
_server.OnClientConnected(new ConnectedMqttClient ClientConnectedCallback?.Invoke(new ConnectedMqttClient
{ {
ClientId = clientId, ClientId = clientId,
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
}); });
await clientSession.Session.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false); await clientSession.RunAsync(connectPacket, clientAdapter).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -87,10 +93,11 @@ namespace MQTTnet.Server
// ignored // ignored
} }
_server.OnClientDisconnected(new ConnectedMqttClient ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient
{ {
ClientId = clientId, ClientId = clientId,
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
PendingApplicationMessages = clientSession?.PendingMessagesQueue.Count ?? 0
}); });
} }
} }
@@ -122,8 +129,9 @@ namespace MQTTnet.Server
{ {
ClientId = s.Value.ClientId, ClientId = s.Value.ClientId,
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311, ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311,
LastPacketReceived = s.Value.LastPacketReceived, LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
LastNonKeepAlivePacketReceived = s.Value.LastNonKeepAlivePacketReceived LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
}).ToList(); }).ToList();
} }
finally finally
@@ -147,7 +155,7 @@ namespace MQTTnet.Server
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false); await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
} }
_server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage); ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -168,18 +176,61 @@ namespace MQTTnet.Server
} }
} }
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (!_sessions.TryGetValue(clientId, out var session))
{
throw new InvalidOperationException($"Client session {clientId} is unknown.");
}
await session.SubscribeAsync(topicFilters);
}
finally
{
_semaphore.Release();
}
}
public async Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (!_sessions.TryGetValue(clientId, out var session))
{
throw new InvalidOperationException($"Client session {clientId} is unknown.");
}
await session.UnsubscribeAsync(topicFilters);
}
finally
{
_semaphore.Release();
}
}
private MqttApplicationMessage InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) private MqttApplicationMessage InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{ {
if (_options.ApplicationMessageInterceptor == null) var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{ {
return applicationMessage; return applicationMessage;
} }
var interceptorContext = new MqttApplicationMessageInterceptorContext( var interceptorContext = new MqttApplicationMessageInterceptorContext(
senderClientSession.ClientId, senderClientSession?.ClientId,
applicationMessage); applicationMessage);
_options.ApplicationMessageInterceptor(interceptorContext); interceptor(interceptorContext);
return interceptorContext.ApplicationMessage; return interceptorContext.ApplicationMessage;
} }
@@ -229,7 +280,14 @@ namespace MQTTnet.Server
{ {
isExistingSession = false; isExistingSession = false;
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, this, _logger); clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger)
{
ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync
};
clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback;
clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback;
_sessions[connectPacket.ClientId] = clientSession; _sessions[connectPacket.ClientId] = clientSession;
_logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId); _logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
@@ -242,5 +300,16 @@ namespace MQTTnet.Server
_semaphore.Release(); _semaphore.Release();
} }
} }
public void Dispose()
{
ClientConnectedCallback = null;
ClientDisconnectedCallback = null;
ClientSubscribedTopicCallback = null;
ClientUnsubscribedTopicCallback = null;
ApplicationMessageReceivedCallback = null;
_semaphore?.Dispose();
}
} }
} }
@@ -0,0 +1,17 @@
using System;
namespace MQTTnet.Server
{
public class MqttClientSubscribedTopicEventArgs : EventArgs
{
public MqttClientSubscribedTopicEventArgs(string clientId, TopicFilter topicFilter)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
}
public string ClientId { get; }
public TopicFilter TopicFilter { get; }
}
}
@@ -8,18 +8,23 @@ using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttClientSubscriptionsManager public sealed class MqttClientSubscriptionsManager : IDisposable
{ {
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly string _clientId;
public MqttClientSubscriptionsManager(IMqttServerOptions options)
public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
} }
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket, string clientId) public Action<string, TopicFilter> TopicSubscribedCallback { get; set; }
public Action<string, string> TopicUnsubscribedCallback { get; set; }
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket)
{ {
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
@@ -34,7 +39,7 @@ namespace MQTTnet.Server
{ {
foreach (var topicFilter in subscribePacket.TopicFilters) foreach (var topicFilter in subscribePacket.TopicFilters)
{ {
var interceptorContext = InterceptSubscribe(clientId, topicFilter); var interceptorContext = InterceptSubscribe(topicFilter);
if (!interceptorContext.AcceptSubscription) if (!interceptorContext.AcceptSubscription)
{ {
result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure);
@@ -52,6 +57,7 @@ namespace MQTTnet.Server
if (interceptorContext.AcceptSubscription) if (interceptorContext.AcceptSubscription)
{ {
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; _subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
TopicSubscribedCallback?.Invoke(_clientId, topicFilter);
} }
} }
} }
@@ -73,6 +79,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in unsubscribePacket.TopicFilters) foreach (var topicFilter in unsubscribePacket.TopicFilters)
{ {
_subscriptions.Remove(topicFilter); _subscriptions.Remove(topicFilter);
TopicUnsubscribedCallback?.Invoke(_clientId, topicFilter);
} }
} }
finally finally
@@ -117,9 +124,25 @@ namespace MQTTnet.Server
} }
} }
private MqttSubscriptionInterceptorContext InterceptSubscribe(string clientId, TopicFilter topicFilter) public void Dispose()
{ {
var interceptorContext = new MqttSubscriptionInterceptorContext(clientId, topicFilter); _semaphore?.Dispose();
}
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel)
{
switch (qualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS0;
case MqttQualityOfServiceLevel.AtLeastOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS1;
case MqttQualityOfServiceLevel.ExactlyOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS2;
default: return MqttSubscribeReturnCode.Failure;
}
}
private MqttSubscriptionInterceptorContext InterceptSubscribe(TopicFilter topicFilter)
{
var interceptorContext = new MqttSubscriptionInterceptorContext(_clientId, topicFilter);
_options.SubscriptionInterceptor?.Invoke(interceptorContext); _options.SubscriptionInterceptor?.Invoke(interceptorContext);
return interceptorContext; return interceptorContext;
} }
@@ -146,16 +169,5 @@ namespace MQTTnet.Server
QualityOfServiceLevel = effectiveQoS QualityOfServiceLevel = effectiveQoS
}; };
} }
private static MqttSubscribeReturnCode ConvertToMaximumQoS(MqttQualityOfServiceLevel qualityOfServiceLevel)
{
switch (qualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS0;
case MqttQualityOfServiceLevel.AtLeastOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS1;
case MqttQualityOfServiceLevel.ExactlyOnce: return MqttSubscribeReturnCode.SuccessMaximumQoS2;
default: return MqttSubscribeReturnCode.Failure;
}
}
} }
} }
@@ -0,0 +1,17 @@
using System;
namespace MQTTnet.Server
{
public class MqttClientUnsubscribedTopicEventArgs : EventArgs
{
public MqttClientUnsubscribedTopicEventArgs(string clientId, string topicFilter)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
}
public string ClientId { get; }
public string TopicFilter { get; }
}
}
@@ -4,11 +4,10 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttRetainedMessagesManager public sealed class MqttRetainedMessagesManager : IDisposable
{ {
private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>(); private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
@@ -68,7 +67,7 @@ namespace MQTTnet.Server
} }
} }
public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(MqttSubscribePacket subscribePacket) public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<TopicFilter> topicFilters)
{ {
var retainedMessages = new List<MqttApplicationMessage>(); var retainedMessages = new List<MqttApplicationMessage>();
@@ -77,13 +76,8 @@ namespace MQTTnet.Server
{ {
foreach (var retainedMessage in _retainedMessages.Values) foreach (var retainedMessage in _retainedMessages.Values)
{ {
foreach (var topicFilter in subscribePacket.TopicFilters) foreach (var topicFilter in topicFilters)
{ {
if (retainedMessage.QualityOfServiceLevel < topicFilter.QualityOfServiceLevel)
{
continue;
}
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
{ {
continue; continue;
@@ -102,6 +96,11 @@ namespace MQTTnet.Server
return retainedMessages; return retainedMessages;
} }
public void Dispose()
{
_semaphore?.Dispose();
}
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage) private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
{ {
var saveIsRequired = false; var saveIsRequired = false;
@@ -16,30 +16,47 @@ namespace MQTTnet.Server
private MqttClientSessionsManager _clientSessionsManager; private MqttClientSessionsManager _clientSessionsManager;
private MqttRetainedMessagesManager _retainedMessagesManager; private MqttRetainedMessagesManager _retainedMessagesManager;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private IMqttServerOptions _options;
public MqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) public MqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
{ {
if (adapters == null) throw new ArgumentNullException(nameof(adapters));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
if (adapters == null)
{
throw new ArgumentNullException(nameof(adapters));
}
_adapters = adapters.ToList(); _adapters = adapters.ToList();
} }
public event EventHandler<MqttServerStartedEventArgs> Started; public event EventHandler<MqttServerStartedEventArgs> Started;
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected; public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
public event EventHandler<MqttClientSubscribedTopicEventArgs> ClientSubscribedTopic;
public event EventHandler<MqttClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopic;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public IMqttServerOptions Options { get; private set; }
public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync() public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
{ {
return _clientSessionsManager.GetConnectedClientsAsync(); return _clientSessionsManager.GetConnectedClientsAsync();
} }
public Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return _clientSessionsManager.SubscribeAsync(clientId, topicFilters);
}
public Task UnsubscribeAsync(string clientId, IList<string> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
}
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages) public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{ {
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
@@ -54,20 +71,28 @@ namespace MQTTnet.Server
public async Task StartAsync(IMqttServerOptions options) public async Task StartAsync(IMqttServerOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started."); if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started.");
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger);
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, this, _logger);
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
await _retainedMessagesManager.LoadMessagesAsync(); await _retainedMessagesManager.LoadMessagesAsync();
_clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger)
{
ClientConnectedCallback = OnClientConnected,
ClientDisconnectedCallback = OnClientDisconnected,
ClientSubscribedTopicCallback = OnClientSubscribedTopic,
ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic,
ApplicationMessageReceivedCallback = OnApplicationMessageReceived
};
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
adapter.ClientAccepted += OnClientAccepted; adapter.ClientAccepted += OnClientAccepted;
await adapter.StartAsync(_options); await adapter.StartAsync(Options);
} }
_logger.Info<MqttServer>("Started."); _logger.Info<MqttServer>("Started.");
@@ -98,39 +123,46 @@ namespace MQTTnet.Server
} }
finally finally
{ {
_clientSessionsManager?.Dispose();
_retainedMessagesManager?.Dispose();
_cancellationTokenSource = null; _cancellationTokenSource = null;
_retainedMessagesManager = null; _retainedMessagesManager = null;
_clientSessionsManager = null; _clientSessionsManager = null;
} }
} }
internal void OnClientConnected(ConnectedMqttClient client) private void OnClientConnected(ConnectedMqttClient client)
{ {
if (client == null) throw new ArgumentNullException(nameof(client));
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId); _logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId);
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
} }
internal void OnClientDisconnected(ConnectedMqttClient client) private void OnClientDisconnected(ConnectedMqttClient client)
{ {
if (client == null) throw new ArgumentNullException(nameof(client));
_logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId); _logger.Info<MqttServer>("Client '{0}': Disconnected.", client.ClientId);
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client)); ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client));
} }
internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter));
}
private void OnClientUnsubscribedTopic(string clientId, string topicFilter)
{
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
}
private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
{
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage)); ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage));
} }
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs) private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
{ {
eventArgs.SessionTask = Task.Run( eventArgs.SessionTask = Task.Run(
async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client, _cancellationTokenSource.Token).ConfigureAwait(false), async () => await _clientSessionsManager.RunSessionAsync(eventArgs.Client, _cancellationTokenSource.Token).ConfigureAwait(false),
_cancellationTokenSource.Token); _cancellationTokenSource.Token);
} }
} }
@@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<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>
<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.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'" />
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
<DefineConstants>RELEASE;NETSTANDARD1_3</DefineConstants>
</PropertyGroup>
<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" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net452'">
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net461'">
</ItemGroup>
</Project>
+6
View File
@@ -1,7 +1,13 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_CALL_PARENTHESES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_CALL_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_PARENTHESES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_PARENTHESES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+3 -2
View File
@@ -18,12 +18,12 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* TLS 1.2 support for client and server (but not UWP servers) * TLS 1.2 support for client and server (but not UWP servers)
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS) * Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
* Lightweight (only the low level implementation of MQTT, no overhead) * Lightweight (only the low level implementation of MQTT, no overhead)
* Performance optimized (processing ~30.000 messages / second)* * Performance optimized (processing ~40.000 messages / second)*
* Interfaces included for mocking and testing * Interfaces included for mocking and testing
* Access to internal trace messages * Access to internal trace messages
* Unit tested (70+ tests) * Unit tested (70+ tests)
\* Tested on local machine with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_.
### Client ### Client
@@ -43,6 +43,7 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* Retained messages are supported including persisting via interface methods (own implementation required) * Retained messages are supported including persisting via interface methods (own implementation required)
* WebSockets supported (via ASP.NET Core 2.0, separate nuget) * WebSockets supported (via ASP.NET Core 2.0, separate nuget)
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Supported frameworks ## Supported frameworks
@@ -0,0 +1,36 @@
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class MqttClientTests
{
[TestMethod]
public async Task ClientDisconnectException()
{
var factory = new MqttFactory();
var client = factory.CreateMqttClient();
var exceptionIsCorrect = false;
client.Disconnected += (s, e) =>
{
exceptionIsCorrect = e.Exception is MqttCommunicationException c && c.InnerException is SocketException;
};
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build());
}
catch
{
}
Assert.IsTrue(exceptionIsCorrect);
}
}
}
@@ -0,0 +1,66 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class MqttKeepAliveMonitorTests
{
[TestMethod]
public void KeepAlive_Timeout()
{
var timeoutCalledCount = 0;
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
{
timeoutCalledCount++;
return Task.FromResult(0);
}, new MqttNetLogger());
Assert.AreEqual(0, timeoutCalledCount);
monitor.Start(1, CancellationToken.None);
Assert.AreEqual(0, timeoutCalledCount);
Thread.Sleep(2000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
Assert.AreEqual(1, timeoutCalledCount);
}
[TestMethod]
public void KeepAlive_NoTimeout()
{
var timeoutCalledCount = 0;
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
{
timeoutCalledCount++;
return Task.FromResult(0);
}, new MqttNetLogger());
Assert.AreEqual(0, timeoutCalledCount);
monitor.Start(1, CancellationToken.None);
Assert.AreEqual(0, timeoutCalledCount);
// Simulate traffic.
Thread.Sleep(1000); // Internally the keep alive timeout is multiplied with 1.5 as per protocol specification.
monitor.PacketReceived(new MqttPublishPacket());
Thread.Sleep(1000);
monitor.PacketReceived(new MqttPublishPacket());
Thread.Sleep(1000);
Assert.AreEqual(0, timeoutCalledCount);
Thread.Sleep(2000);
Assert.AreEqual(1, timeoutCalledCount);
}
}
}
@@ -0,0 +1,32 @@
using Microsoft.VisualStudio.TestTools .UnitTesting;
using MQTTnet.Client;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class MqttPacketIdentifierProviderTests
{
[TestMethod]
public void Reset()
{
var p = new MqttPacketIdentifierProvider();
Assert.AreEqual(1, p.GetNewPacketIdentifier());
Assert.AreEqual(2, p.GetNewPacketIdentifier());
p.Reset();
Assert.AreEqual(1, p.GetNewPacketIdentifier());
}
[TestMethod]
public void ReachBoundaries()
{
var p = new MqttPacketIdentifierProvider();
for (ushort i = 0; i < ushort.MaxValue; i++)
{
Assert.AreEqual(i + 1, p.GetNewPacketIdentifier());
}
Assert.AreEqual(1, p.GetNewPacketIdentifier());
}
}
}
@@ -12,7 +12,7 @@ namespace MQTTnet.Core.Tests
public void MqttPacketReader_EmptyStream() public void MqttPacketReader_EmptyStream()
{ {
var memStream = new MemoryStream(); var memStream = new MemoryStream();
var header = MqttPacketReader.ReadHeaderFromSource(memStream, CancellationToken.None); var header = MqttPacketReader.ReadHeaderAsync(memStream, CancellationToken.None).GetAwaiter().GetResult();
Assert.IsNull(header); Assert.IsNull(header);
} }
@@ -119,10 +119,10 @@ namespace MQTTnet.Core.Tests
{ {
var p = new MqttConnAckPacket var p = new MqttConnAckPacket
{ {
ConnectReturnCode = MqttConnectReturnCode.ConnectionAccepted ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized
}; };
SerializeAndCompare(p, "IAIAAA==", MqttProtocolVersion.V310); SerializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310);
} }
[TestMethod] [TestMethod]
@@ -137,6 +137,17 @@ namespace MQTTnet.Core.Tests
DeserializeAndCompare(p, "IAIBBQ=="); DeserializeAndCompare(p, "IAIBBQ==");
} }
[TestMethod]
public void DeserializeV310_MqttConnAckPacket()
{
var p = new MqttConnAckPacket
{
ConnectReturnCode = MqttConnectReturnCode.ConnectionRefusedNotAuthorized
};
DeserializeAndCompare(p, "IAIABQ==", MqttProtocolVersion.V310);
}
[TestMethod] [TestMethod]
public void SerializeV311_MqttDisconnectPacket() public void SerializeV311_MqttDisconnectPacket()
{ {
@@ -397,19 +408,19 @@ namespace MQTTnet.Core.Tests
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks))); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks)));
} }
private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value) private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{ {
var serializer = new MqttPacketSerializer(); var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var buffer1 = serializer.Serialize(packet); var buffer1 = serializer.Serialize(packet);
using (var headerStream = new MemoryStream(Join(buffer1))) using (var headerStream = new MemoryStream(Join(buffer1)))
{ {
var header = MqttPacketReader.ReadHeaderFromSource(headerStream, CancellationToken.None); var header = MqttPacketReader.ReadHeaderAsync(headerStream, CancellationToken.None).GetAwaiter().GetResult();
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength)) using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength))
{ {
var deserializedPacket = serializer.Deserialize(header, bodyStream.ToArray()); var deserializedPacket = serializer.Deserialize(header, bodyStream);
var buffer2 = serializer.Serialize(deserializedPacket); var buffer2 = serializer.Serialize(deserializedPacket);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2))); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(buffer2)));
+55 -2
View File
@@ -79,7 +79,7 @@ namespace MQTTnet.Core.Tests
} }
[TestMethod] [TestMethod]
public async Task MqttServer_Unsubscribe() public async Task MqttServer_SubscribeUnsubscribe()
{ {
var serverAdapter = new TestMqttServerAdapter(); var serverAdapter = new TestMqttServerAdapter();
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
@@ -99,13 +99,31 @@ namespace MQTTnet.Core.Tests
await c2.PublishAsync(message); await c2.PublishAsync(message);
Assert.AreEqual(0, receivedMessagesCount); Assert.AreEqual(0, receivedMessagesCount);
var subscribeEventCalled = false;
s.ClientSubscribedTopic += (_, e) =>
{
subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == "c1";
};
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce)); await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));
await Task.Delay(100);
Assert.IsTrue(subscribeEventCalled, "Subscribe event not called.");
await c2.PublishAsync(message); await c2.PublishAsync(message);
await Task.Delay(500); await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
var unsubscribeEventCalled = false;
s.ClientUnsubscribedTopic += (_, e) =>
{
unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == "c1";
};
await c1.UnsubscribeAsync("a"); await c1.UnsubscribeAsync("a");
await Task.Delay(100);
Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called.");
await c2.PublishAsync(message); await c2.PublishAsync(message);
await Task.Delay(500); await Task.Delay(500);
@@ -299,7 +317,7 @@ namespace MQTTnet.Core.Tests
var serverAdapter = new TestMqttServerAdapter(); var serverAdapter = new TestMqttServerAdapter();
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger()); var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
try try
{ {
var options = new MqttServerOptions { ApplicationMessageInterceptor = Interceptor }; var options = new MqttServerOptions { ApplicationMessageInterceptor = Interceptor };
@@ -330,6 +348,41 @@ namespace MQTTnet.Core.Tests
} }
} }
[TestMethod]
public async Task MqttServer_Body()
{
var serverAdapter = new TestMqttServerAdapter();
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
var bodyIsMatching = false;
try
{
await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
c1.ApplicationMessageReceived += (_, e) =>
{
if (Encoding.UTF8.GetString(e.ApplicationMessage.Payload) == "The body")
{
bodyIsMatching = true;
}
};
await c1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtMostOnce);
await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("A").WithPayload(Encoding.UTF8.GetBytes("The body")).Build());
await Task.Delay(500);
}
finally
{
await s.StopAsync();
}
Assert.IsTrue(bodyIsMatching);
}
private class TestStorage : IMqttServerStorage private class TestStorage : IMqttServerStorage
{ {
private IList<MqttApplicationMessage> _messages = new List<MqttApplicationMessage>(); private IList<MqttApplicationMessage> _messages = new List<MqttApplicationMessage>();
@@ -11,12 +11,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeSingleSuccess() public void MqttSubscriptionsManager_SubscribeSingleSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
sm.SubscribeAsync(sp, "").Wait(); sm.SubscribeAsync(sp).Wait();
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -32,12 +32,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce));
sm.SubscribeAsync(sp, "").Wait(); sm.SubscribeAsync(sp).Wait();
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -53,13 +53,13 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess() public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce));
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce)); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce));
sm.SubscribeAsync(sp, "").Wait(); sm.SubscribeAsync(sp).Wait();
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -75,12 +75,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeSingleNoSuccess() public void MqttSubscriptionsManager_SubscribeSingleNoSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
sm.SubscribeAsync(sp, "").Wait(); sm.SubscribeAsync(sp).Wait();
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -94,12 +94,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions()); var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), "");
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
sm.SubscribeAsync(sp, "").Wait(); sm.SubscribeAsync(sp).Wait();
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -10,7 +10,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -6,7 +6,7 @@
}, },
"dependencies": { "dependencies": {
"mqtt": "2.13.1", "mqtt": "2.15.1",
"@types/node": "8.0.46", "@types/node": "8.0.46",
"systemjs": "0.20.19", "systemjs": "0.20.19",
"typescript": "2.3.4" "typescript": "2.3.4"
@@ -138,10 +138,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>5.4.0</Version> <Version>6.0.8</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls"> <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>2.0.0</Version> <Version>2.2.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">