@@ -197,6 +197,7 @@ ClientBin/ | |||
*.dbmdl | |||
*.dbproj.schemaview | |||
*.jfm | |||
# *.pfx | |||
*.publishsettings | |||
orleans.codegen.cs | |||
@@ -290,3 +291,5 @@ __pycache__/ | |||
Build/nuget.exe | |||
*.js | |||
*.map | |||
/Tests/MQTTnet.TestApp.NetCore/RetainedMessages.json |
@@ -2,7 +2,7 @@ | |||
<package > | |||
<metadata> | |||
<id>MQTTnet.AspNetCore</id> | |||
<version>2.5.0</version> | |||
<version>2.7.5</version> | |||
<authors>Christian Kratky</authors> | |||
<owners>Christian Kratky</owners> | |||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | |||
@@ -10,14 +10,13 @@ | |||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | |||
<requireLicenseAcceptance>false</requireLicenseAcceptance> | |||
<description>This is a support library to integrate MQTTnet into AspNetCore.</description> | |||
<releaseNotes>initial version | |||
<releaseNotes>* Updated to MQTTnet 2.7.5. | |||
</releaseNotes> | |||
<copyright>Copyright Christian Kratky 2016-2017</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation</tags> | |||
<copyright>Copyright Christian Kratky 2016-2018</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<dependencies> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="MQTTnet" version="2.5" /> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
</dependencies> | |||
</metadata> | |||
@@ -25,6 +24,5 @@ | |||
<files> | |||
<!-- .NET Standard 2.0 --> | |||
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/> | |||
</files> | |||
</package> |
@@ -0,0 +1,47 @@ | |||
<?xml version="1.0"?> | |||
<package > | |||
<metadata> | |||
<id>MQTTnet.Extensions.Rpc</id> | |||
<version>2.7.5</version> | |||
<authors>Christian Kratky</authors> | |||
<owners>Christian Kratky</owners> | |||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | |||
<projectUrl>https://github.com/chkr1011/MQTTnet</projectUrl> | |||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | |||
<requireLicenseAcceptance>false</requireLicenseAcceptance> | |||
<description>This is a extension library which allows executing synchronous device calls including a response using MQTTnet.</description> | |||
<releaseNotes>* Updated to MQTTnet 2.7.5. | |||
</releaseNotes> | |||
<copyright>Copyright Christian Kratky 2016-2017</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<dependencies> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
<group targetFramework="netstandard1.3"> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
<group targetFramework="uap10.0"> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
<group targetFramework="net452"> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
<group targetFramework="net461"> | |||
<dependency id="MQTTnet" version="2.7.5" /> | |||
</group> | |||
</dependencies> | |||
</metadata> | |||
<files> | |||
<!-- .NET Standard 2.0 --> | |||
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/> | |||
</files> | |||
</package> |
@@ -2,7 +2,7 @@ | |||
<package > | |||
<metadata> | |||
<id>MQTTnet</id> | |||
<version>2.5.0</version> | |||
<version>2.7.5</version> | |||
<authors>Christian Kratky</authors> | |||
<owners>Christian Kratky</owners> | |||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> | |||
@@ -10,49 +10,59 @@ | |||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> | |||
<requireLicenseAcceptance>false</requireLicenseAcceptance> | |||
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> | |||
<releaseNotes>* [Core] Merged the .NET Framwork and netstandard projects (Thanks to @JanEggers) | |||
* [Core] Migrated the trace to a non-static approach (Breaking Change!) | |||
* [Core] Added a builder for application messages using a fluent API | |||
* [Core] Introduced CI (Thanks to @JanEggers) | |||
* [Core] Added interfaces for publishing and receiving which applies to every client and server (Thanks to @ChristianRiedl) | |||
* [Client] Added a first version of a managed client which will manage the connection, subscription etc. automatically (Thanks to @JTrotta) | |||
* [Client] The session state response from the server is now returned in the _ConnectAsync_ method and also part of the _Connected_ event args | |||
* [Client] Added a _TopicFilterBuilder_ using a fluent API (Namespace Changes!) | |||
* [Client] Added several new options for the WebSocket channel (Thanks to @ChristianRiedl) | |||
* [Client] Refactored the options and added a builder using a fluent API (Breaking Change!) | |||
* [Client] Added more options for WebSocket connections like RequestHeaders, SubProtocol etc. | |||
* [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl) | |||
* [Server] Added support for a custom application message interceptor | |||
* [Server] Fixed an issue with dropped connections on UWP (Thanks to @haeberle) | |||
<releaseNotes> * [Client] Fixed a deadlock while the client disconnects. | |||
* [Client] Fixed broken support for protocol version 3.1.0. | |||
* [Server] The _MqttTcpServerAdapter_ is now added to the ASP.NET services. | |||
* [Server] _MqttServerAdapter_ is renamed to _MqttTcpServerAdapter_ (BREAKING CHANGE!). | |||
* [Server] The server no longer sends the will message of a client if the disconnect was clean (via _Disconnect_ packet). | |||
* [Server] The application message interceptor now allows closing the connection. | |||
* [Server] Added a new flag for the _ClientDisconnected_ event which contains a value indicating whether the disconnect was clean (via _Disconnect_ packet). | |||
</releaseNotes> | |||
<copyright>Copyright Christian Kratky 2016-2017</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation</tags> | |||
<copyright>Copyright Christian Kratky 2016-2018</copyright> | |||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> | |||
<dependencies> | |||
<group targetFramework="netstandard1.3"> | |||
<dependency id="NETStandard.Library" version="1.6.1" /> | |||
<dependency id="NETStandard.Library" version="1.3.0" /> | |||
<dependency id="System.Net.Security" version="4.3.2" /> | |||
<dependency id="System.Net.WebSockets" version="4.3.0" /> | |||
<dependency id="System.Net.WebSockets.Client" version="4.3.1" /> | |||
</group> | |||
<group targetFramework="netstandard2.0"> | |||
<dependency id="NETStandard.Library" version="2.0.0" /> | |||
<dependency id="System.Net.Security" version="4.3.2" /> | |||
<dependency id="System.Net.WebSockets" version="4.3.0" /> | |||
<dependency id="System.Net.WebSockets.Client" version="4.3.1" /> | |||
</group> | |||
<group targetFramework="uap10.0"> | |||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.0" /> | |||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" /> | |||
</group> | |||
<group targetFramework="net452"> | |||
</group> | |||
<group targetFramework="net461"> | |||
</group> | |||
</dependencies> | |||
</metadata> | |||
<files> | |||
<!-- .NET Standard 1.3 --> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.Core.*" target="lib\netstandard1.3\"/> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/> | |||
<!-- .NET Standard 2.0 --> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard2.0\MQTTnet.*" target="lib\netstandard2.0\"/> | |||
<!-- Universal Windows --> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\uap10.0\MQTTnet.Core.*" target="lib\uap10.0\"/> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\uap10.0\MQTTnet.*" target="lib\uap10.0\"/> | |||
<!-- .NET Framework --> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net451\MQTTnet.Core.*" target="lib\net451\"/> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net451\MQTTnet.*" target="lib\net451\"/> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/> | |||
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/> | |||
</files> | |||
</package> |
@@ -1,6 +1,7 @@ | |||
param([string]$version) | |||
param([string]$assemblyVersion, [string]$nugetVersion) | |||
if ([string]::IsNullOrEmpty($version)) {$version = "0.0.1"} | |||
if ([string]::IsNullOrEmpty($assemblyVersion)) {$assemblyVersion = "0.0.1"} | |||
if ([string]::IsNullOrEmpty($nugetVersion)) {$nugetVersion = "0.0.1"} | |||
$vswhere = ${Env:\ProgramFiles(x86)} + '\Microsoft Visual Studio\Installer\vswhere' | |||
@@ -8,14 +9,27 @@ $path = &$vswhere -latest -products * -requires Microsoft.Component.MSBuild -pro | |||
if ($path) { | |||
$msbuild = join-path $path 'MSBuild\15.0\Bin\MSBuild.exe' | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net451" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$version /p:AssemblyVersion=$version /verbosity:m | |||
&dotnet build ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj -c="Release" /p:FileVersion=$version /p:AssemblyVersion=$version | |||
# Build the core library | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" | |||
&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" | |||
# Build the ASP.NET Core 2.0 extension | |||
&$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" | |||
Remove-Item .\NuGet -Force -Recurse | |||
New-Item -ItemType Directory -Force -Path .\NuGet | |||
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $version | |||
.\NuGet.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $version | |||
} | |||
# Build the RPC extension | |||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m | |||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m | |||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m | |||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m | |||
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m | |||
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue | |||
New-Item -ItemType Directory -Force -Path .\NuGet | |||
.\NuGet.exe pack MQTTnet.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\NuGet.exe pack MQTTnet.AspNetCore.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
.\NuGet.exe pack MQTTnet.Extensions.Rpc.nuspec -Verbosity detailed -Symbols -OutputDir "NuGet" -Version $nugetVersion | |||
} |
@@ -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 |
@@ -0,0 +1,31 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFrameworks>netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> | |||
<AssemblyVersion>0.0.0.0</AssemblyVersion> | |||
<FileVersion>0.0.0.0</FileVersion> | |||
<Product /> | |||
<Company /> | |||
<Authors /> | |||
<PackageId /> | |||
<Version>0.0.0.0</Version> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> | |||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> | |||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> | |||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> | |||
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion> | |||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion> | |||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> | |||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> | |||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> | |||
<DefaultLanguage>en</DefaultLanguage> | |||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" /> | |||
</ItemGroup> | |||
</Project> |
@@ -0,0 +1,101 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Client; | |||
using MQTTnet.Internal; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Extensions.Rpc | |||
{ | |||
public sealed class MqttRpcClient : IDisposable | |||
{ | |||
private const string ResponseTopic = "$MQTTnet.RPC/+/+/response"; | |||
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>(); | |||
private readonly IMqttClient _mqttClient; | |||
private bool _isEnabled; | |||
public MqttRpcClient(IMqttClient mqttClient) | |||
{ | |||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); | |||
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; | |||
} | |||
public async Task EnableAsync() | |||
{ | |||
await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build()); | |||
_isEnabled = true; | |||
} | |||
public async Task DisableAsync() | |||
{ | |||
await _mqttClient.UnsubscribeAsync(ResponseTopic); | |||
_isEnabled = false; | |||
} | |||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
{ | |||
if (methodName == null) throw new ArgumentNullException(nameof(methodName)); | |||
if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#")) | |||
{ | |||
throw new ArgumentException("The method name cannot contain /, + or #."); | |||
} | |||
if (!_isEnabled) | |||
{ | |||
throw new InvalidOperationException("The RPC client is not enabled."); | |||
} | |||
var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}"; | |||
var responseTopic = requestTopic + "/response"; | |||
var requestMessage = new MqttApplicationMessageBuilder() | |||
.WithTopic(requestTopic) | |||
.WithPayload(payload) | |||
.WithQualityOfServiceLevel(qualityOfServiceLevel) | |||
.Build(); | |||
try | |||
{ | |||
var tcs = new TaskCompletionSource<byte[]>(); | |||
if (!_waitingCalls.TryAdd(responseTopic, tcs)) | |||
{ | |||
throw new InvalidOperationException(); | |||
} | |||
await _mqttClient.PublishAsync(requestMessage); | |||
return await tcs.Task.TimeoutAfter(timeout); | |||
} | |||
finally | |||
{ | |||
_waitingCalls.TryRemove(responseTopic, out _); | |||
} | |||
} | |||
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) | |||
{ | |||
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource<byte[]> tcs)) | |||
{ | |||
return; | |||
} | |||
if (tcs.Task.IsCompleted || tcs.Task.IsCanceled) | |||
{ | |||
return; | |||
} | |||
tcs.TrySetResult(eventArgs.ApplicationMessage.Payload); | |||
} | |||
public void Dispose() | |||
{ | |||
foreach (var tcs in _waitingCalls) | |||
{ | |||
tcs.Value.SetCanceled(); | |||
} | |||
_waitingCalls.Clear(); | |||
} | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
|
@@ -1,7 +1,8 @@ | |||
using System; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using MQTTnet.Core.Server; | |||
using System.Linq; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.AspNetCore | |||
{ | |||
@@ -14,8 +15,18 @@ namespace MQTTnet.AspNetCore | |||
{ | |||
if (context.Request.Path == path && context.WebSockets.IsWebSocketRequest) | |||
{ | |||
string subprotocol = null; | |||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues) | |||
&& requestedSubProtocolValues.Count > 0 | |||
&& requestedSubProtocolValues.Any(v => v.ToLower() == "mqtt") | |||
) | |||
{ | |||
subprotocol = "mqtt"; | |||
} | |||
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>(); | |||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync("mqtt")) | |||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subprotocol)) | |||
{ | |||
await adapter.AcceptWebSocketAsync(webSocket); | |||
} | |||
@@ -2,12 +2,23 @@ | |||
<PropertyGroup> | |||
<TargetFramework>netstandard2.0</TargetFramework> | |||
<AssemblyVersion>0.0.0.0</AssemblyVersion> | |||
<FileVersion>0.0.0.0</FileVersion> | |||
<Version>0.0.0.0</Version> | |||
<Product /> | |||
<Company /> | |||
<Authors /> | |||
<PackageId /> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | |||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.1" /> | |||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.0.1" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -1,24 +1,26 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using MQTTnet.Core.Adapter; | |||
using MQTTnet.Core.Server; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.AspNetCore | |||
{ | |||
public class MqttHostedServer : MqttServer, IHostedService | |||
{ | |||
public MqttHostedServer(IOptions<MqttServerOptions> options, IEnumerable<IMqttServerAdapter> adapters, ILogger<MqttServer> logger, MqttClientSessionsManager clientSessionsManager) | |||
: base(options, adapters, logger, clientSessionsManager) | |||
private readonly IMqttServerOptions _options; | |||
public MqttHostedServer(IMqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(adapters, logger) | |||
{ | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
} | |||
public Task StartAsync(CancellationToken cancellationToken) | |||
{ | |||
return StartAsync(); | |||
return StartAsync(_options); | |||
} | |||
public Task StopAsync(CancellationToken cancellationToken) | |||
@@ -1,27 +1,18 @@ | |||
using System; | |||
using System.IO; | |||
using System.Net.WebSockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Adapter; | |||
using MQTTnet.Core.Channel; | |||
using MQTTnet.Core.Server; | |||
using MQTTnet.Implementations; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Serializer; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.AspNetCore | |||
{ | |||
public class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable | |||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable | |||
{ | |||
private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; | |||
public MqttWebSocketServerAdapter(IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) | |||
{ | |||
_mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); | |||
} | |||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; | |||
public Task StartAsync(MqttServerOptions options) | |||
public Task StartAsync(IMqttServerOptions options) | |||
{ | |||
return Task.CompletedTask; | |||
} | |||
@@ -36,7 +27,7 @@ namespace MQTTnet.AspNetCore | |||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); | |||
var channel = new MqttWebSocketServerChannel(webSocket); | |||
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(channel); | |||
var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketSerializer(), new MqttNetLogger()); | |||
var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); | |||
ClientAccepted?.Invoke(this, eventArgs); | |||
@@ -47,47 +38,5 @@ namespace MQTTnet.AspNetCore | |||
{ | |||
StopAsync(); | |||
} | |||
private class MqttWebSocketServerChannel : IMqttCommunicationChannel, IDisposable | |||
{ | |||
private readonly WebSocket _webSocket; | |||
public MqttWebSocketServerChannel(WebSocket webSocket) | |||
{ | |||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); | |||
RawReceiveStream = new WebSocketStream(_webSocket); | |||
} | |||
public Stream SendStream => RawReceiveStream; | |||
public Stream ReceiveStream => RawReceiveStream; | |||
public Stream RawReceiveStream { get; } | |||
public Task ConnectAsync() | |||
{ | |||
return Task.CompletedTask; | |||
} | |||
public Task DisconnectAsync() | |||
{ | |||
RawReceiveStream?.Dispose(); | |||
if (_webSocket == null) | |||
{ | |||
return Task.CompletedTask; | |||
} | |||
return _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); | |||
} | |||
public void Dispose() | |||
{ | |||
RawReceiveStream?.Dispose(); | |||
SendStream?.Dispose(); | |||
ReceiveStream?.Dispose(); | |||
_webSocket?.Dispose(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
using System; | |||
using System.IO; | |||
using System.Net.WebSockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Channel; | |||
using MQTTnet.Implementations; | |||
namespace MQTTnet.AspNetCore | |||
{ | |||
public class MqttWebSocketServerChannel : IMqttChannel, IDisposable | |||
{ | |||
private WebSocket _webSocket; | |||
public MqttWebSocketServerChannel(WebSocket webSocket) | |||
{ | |||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); | |||
SendStream = new WebSocketStream(_webSocket); | |||
ReceiveStream = SendStream; | |||
} | |||
public Stream SendStream { get; private set; } | |||
public Stream ReceiveStream { get; private set; } | |||
public Task ConnectAsync() | |||
{ | |||
return Task.CompletedTask; | |||
} | |||
public async Task DisconnectAsync() | |||
{ | |||
if (_webSocket == null) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); | |||
} | |||
finally | |||
{ | |||
Dispose(); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
SendStream?.Dispose(); | |||
ReceiveStream?.Dispose(); | |||
_webSocket?.Dispose(); | |||
SendStream = null; | |||
ReceiveStream = null; | |||
_webSocket = null; | |||
} | |||
} | |||
} |
@@ -1,22 +1,29 @@ | |||
using Microsoft.Extensions.DependencyInjection; | |||
using System; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Hosting; | |||
using MQTTnet.Core.Adapter; | |||
using MQTTnet.Core.Server; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Server; | |||
using MQTTnet.Implementations; | |||
namespace MQTTnet.AspNetCore | |||
{ | |||
public static class ServiceCollectionExtensions | |||
{ | |||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services) | |||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, IMqttServerOptions options) | |||
{ | |||
services.AddMqttServerServices(); | |||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||
services.AddSingleton(options); | |||
services.AddSingleton<IMqttNetLogger>(new MqttNetLogger()); | |||
services.AddSingleton<MqttHostedServer>(); | |||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>()); | |||
services.AddSingleton<IMqttServer>(s => s.GetService<MqttHostedServer>()); | |||
services.AddSingleton<MqttHostedServer>(); | |||
services.AddSingleton<MqttWebSocketServerAdapter>(); | |||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>()); | |||
services.AddSingleton<MqttTcpServerAdapter>(); | |||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>()); | |||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>()); | |||
return services; | |||
} | |||
@@ -2,12 +2,12 @@ | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Packets; | |||
using MQTTnet.Core.Serializer; | |||
using MQTTnet.Packets; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Adapter | |||
namespace MQTTnet.Adapter | |||
{ | |||
public interface IMqttCommunicationAdapter | |||
public interface IMqttChannelAdapter : IDisposable | |||
{ | |||
IMqttPacketSerializer PacketSerializer { get; } | |||
@@ -1,14 +1,14 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Server; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.Core.Adapter | |||
namespace MQTTnet.Adapter | |||
{ | |||
public interface IMqttServerAdapter | |||
{ | |||
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; | |||
Task StartAsync(MqttServerOptions options); | |||
Task StartAsync(IMqttServerOptions options); | |||
Task StopAsync(); | |||
} | |||
} |
@@ -0,0 +1,255 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Net.Sockets; | |||
using System.Runtime.InteropServices; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Channel; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Internal; | |||
using MQTTnet.Packets; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Adapter | |||
{ | |||
public sealed class MqttChannelAdapter : IMqttChannelAdapter | |||
{ | |||
private const uint ErrorOperationAborted = 0x800703E3; | |||
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config | |||
private bool _isDisposed; | |||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||
private readonly IMqttNetLogger _logger; | |||
private readonly IMqttChannel _channel; | |||
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_channel = channel ?? throw new ArgumentNullException(nameof(channel)); | |||
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); | |||
} | |||
public IMqttPacketSerializer PacketSerializer { get; } | |||
public Task ConnectAsync(TimeSpan timeout) | |||
{ | |||
ThrowIfDisposed(); | |||
_logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout); | |||
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); | |||
} | |||
public Task DisconnectAsync(TimeSpan timeout) | |||
{ | |||
ThrowIfDisposed(); | |||
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout); | |||
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); | |||
} | |||
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets) | |||
{ | |||
ThrowIfDisposed(); | |||
return ExecuteAndWrapExceptionAsync(async () => | |||
{ | |||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); | |||
try | |||
{ | |||
foreach (var packet in packets) | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
return; | |||
} | |||
if (packet == null) | |||
{ | |||
continue; | |||
} | |||
_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout); | |||
var chunks = PacketSerializer.Serialize(packet); | |||
foreach (var chunk in chunks) | |||
{ | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
return; | |||
} | |||
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false); | |||
} | |||
} | |||
if (cancellationToken.IsCancellationRequested) | |||
{ | |||
return; | |||
} | |||
if (timeout > TimeSpan.Zero) | |||
{ | |||
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false); | |||
} | |||
} | |||
finally | |||
{ | |||
_semaphore.Release(); | |||
} | |||
}); | |||
} | |||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) | |||
{ | |||
ThrowIfDisposed(); | |||
MqttBasePacket packet = null; | |||
await ExecuteAndWrapExceptionAsync(async () => | |||
{ | |||
ReceivedMqttPacket receivedMqttPacket = null; | |||
try | |||
{ | |||
if (timeout > TimeSpan.Zero) | |||
{ | |||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false); | |||
} | |||
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) | |||
{ | |||
throw new TaskCanceledException(); | |||
} | |||
packet = PacketSerializer.Deserialize(receivedMqttPacket.Header, receivedMqttPacket.Body); | |||
if (packet == null) | |||
{ | |||
throw new MqttProtocolViolationException("Received malformed packet."); | |||
} | |||
_logger.Verbose<MqttChannelAdapter>("RX <<< {0}", packet); | |||
} | |||
finally | |||
{ | |||
receivedMqttPacket?.Dispose(); | |||
} | |||
}).ConfigureAwait(false); | |||
return packet; | |||
} | |||
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken) | |||
{ | |||
var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false); | |||
if (header == null) | |||
{ | |||
return null; | |||
} | |||
if (header.BodyLength == 0) | |||
{ | |||
return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false)); | |||
} | |||
var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream(); | |||
var buffer = new byte[ReadBufferSize]; | |||
while (body.Length < header.BodyLength) | |||
{ | |||
var bytesLeft = header.BodyLength - (int)body.Length; | |||
if (bytesLeft > buffer.Length) | |||
{ | |||
bytesLeft = buffer.Length; | |||
} | |||
var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); | |||
// Check if the client closed the connection before sending the full body. | |||
if (readBytesCount == 0) | |||
{ | |||
throw new MqttCommunicationException("Connection closed while reading remaining packet body."); | |||
} | |||
// Here is no need to await because internally only an array is used and no real I/O operation is made. | |||
// Using async here will only generate overhead. | |||
body.Write(buffer, 0, readBytesCount); | |||
} | |||
body.Seek(0L, SeekOrigin.Begin); | |||
return new ReceivedMqttPacket(header, body); | |||
} | |||
private static async Task ExecuteAndWrapExceptionAsync(Func<Task> action) | |||
{ | |||
try | |||
{ | |||
await action().ConfigureAwait(false); | |||
} | |||
catch (TaskCanceledException) | |||
{ | |||
throw; | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
throw; | |||
} | |||
catch (MqttCommunicationTimedOutException) | |||
{ | |||
throw; | |||
} | |||
catch (MqttCommunicationException) | |||
{ | |||
throw; | |||
} | |||
catch (COMException comException) | |||
{ | |||
if ((uint)comException.HResult == ErrorOperationAborted) | |||
{ | |||
throw new OperationCanceledException(); | |||
} | |||
throw new MqttCommunicationException(comException); | |||
} | |||
catch (IOException exception) | |||
{ | |||
if (exception.InnerException is SocketException socketException) | |||
{ | |||
if (socketException.SocketErrorCode == SocketError.ConnectionAborted) | |||
{ | |||
throw new OperationCanceledException(); | |||
} | |||
} | |||
throw new MqttCommunicationException(exception); | |||
} | |||
catch (Exception exception) | |||
{ | |||
throw new MqttCommunicationException(exception); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
_isDisposed = true; | |||
_semaphore?.Dispose(); | |||
_channel?.Dispose(); | |||
} | |||
private void ThrowIfDisposed() | |||
{ | |||
if (_isDisposed) | |||
{ | |||
throw new ObjectDisposedException(nameof(MqttChannelAdapter)); | |||
} | |||
} | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
using MQTTnet.Core.Exceptions; | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.Adapter | |||
namespace MQTTnet.Adapter | |||
{ | |||
public class MqttConnectingFailedException : MqttCommunicationException | |||
{ |
@@ -1,16 +1,16 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Core.Adapter | |||
namespace MQTTnet.Adapter | |||
{ | |||
public class MqttServerAdapterClientAcceptedEventArgs : EventArgs | |||
{ | |||
public MqttServerAdapterClientAcceptedEventArgs(IMqttCommunicationAdapter client) | |||
public MqttServerAdapterClientAcceptedEventArgs(IMqttChannelAdapter client) | |||
{ | |||
Client = client ?? throw new ArgumentNullException(nameof(client)); | |||
} | |||
public IMqttCommunicationAdapter Client { get; } | |||
public IMqttChannelAdapter Client { get; } | |||
public Task SessionTask { get; set; } | |||
} |
@@ -1,10 +1,10 @@ | |||
using System; | |||
using System.IO; | |||
using MQTTnet.Core.Packets; | |||
using MQTTnet.Packets; | |||
namespace MQTTnet.Core.Adapter | |||
namespace MQTTnet.Adapter | |||
{ | |||
public class ReceivedMqttPacket | |||
public sealed class ReceivedMqttPacket : IDisposable | |||
{ | |||
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body) | |||
{ | |||
@@ -15,5 +15,10 @@ namespace MQTTnet.Core.Adapter | |||
public MqttPacketHeader Header { get; } | |||
public MemoryStream Body { get; } | |||
public void Dispose() | |||
{ | |||
Body?.Dispose(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet | |||
{ | |||
public static class ApplicationMessagePublisherExtensions | |||
{ | |||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, params MqttApplicationMessage[] applicationMessages) | |||
{ | |||
if (publisher == null) throw new ArgumentNullException(nameof(publisher)); | |||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); | |||
return publisher.PublishAsync(applicationMessages); | |||
} | |||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic) | |||
{ | |||
if (publisher == null) throw new ArgumentNullException(nameof(publisher)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).Build()); | |||
} | |||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload) | |||
{ | |||
if (publisher == null) throw new ArgumentNullException(nameof(publisher)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).Build()); | |||
} | |||
public static Task PublishAsync(this IApplicationMessagePublisher publisher, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
{ | |||
if (publisher == null) throw new ArgumentNullException(nameof(publisher)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return publisher.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); | |||
} | |||
} | |||
} |
@@ -1,13 +1,13 @@ | |||
using System.Threading.Tasks; | |||
using System; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Core.Channel | |||
namespace MQTTnet.Channel | |||
{ | |||
public interface IMqttCommunicationChannel | |||
public interface IMqttChannel : IDisposable | |||
{ | |||
Stream SendStream { get; } | |||
Stream ReceiveStream { get; } | |||
Stream RawReceiveStream { get; } | |||
Task ConnectAsync(); | |||
Task DisconnectAsync(); |
@@ -1,11 +1,10 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Packets; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher | |||
public interface IMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable | |||
{ | |||
bool IsConnected { get; } | |||
@@ -16,6 +15,6 @@ namespace MQTTnet.Core.Client | |||
Task DisconnectAsync(); | |||
Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters); | |||
Task UnsubscribeAsync(IEnumerable<string> topicFilters); | |||
Task UnsubscribeAsync(IEnumerable<string> topics); | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
namespace MQTTnet.Client | |||
{ | |||
public interface IMqttClientAdapterFactory | |||
{ | |||
IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger); | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public interface IMqttClientChannelOptions | |||
{ |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public interface IMqttClientCredentials | |||
{ |
@@ -0,0 +1,16 @@ | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.ManagedClient; | |||
namespace MQTTnet.Client | |||
{ | |||
public interface IMqttClientFactory | |||
{ | |||
IMqttClient CreateMqttClient(); | |||
IMqttClient CreateMqttClient(IMqttNetLogger logger); | |||
IManagedMqttClient CreateManagedMqttClient(); | |||
IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger); | |||
} | |||
} |
@@ -1,22 +1,20 @@ | |||
using System; | |||
using MQTTnet.Core.Serializer; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public interface IMqttClientOptions | |||
{ | |||
string ClientId { get; } | |||
/// <summary> | |||
/// The LogId is used to create a scope to correlate logging. If no value is provided the ClientId is used instead | |||
/// </summary> | |||
string LogId { get; } | |||
IMqttClientCredentials Credentials { get; } | |||
bool CleanSession { get; } | |||
MqttApplicationMessage WillMessage { get; } | |||
TimeSpan CommunicationTimeout { get; } | |||
TimeSpan KeepAlivePeriod { get; } | |||
TimeSpan? KeepAliveSendInterval { get; } | |||
MqttProtocolVersion ProtocolVersion { get; } | |||
IMqttClientChannelOptions ChannelOptions { get; } |
@@ -0,0 +1,516 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Internal; | |||
using MQTTnet.Packets; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClient : IMqttClient | |||
{ | |||
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); | |||
private readonly Stopwatch _sendTracker = new Stopwatch(); | |||
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1); | |||
private readonly IMqttClientAdapterFactory _adapterFactory; | |||
private readonly MqttPacketDispatcher _packetDispatcher; | |||
private readonly IMqttNetLogger _logger; | |||
private IMqttClientOptions _options; | |||
private bool _isReceivingPackets; | |||
private CancellationTokenSource _cancellationTokenSource; | |||
private Task _packetReceiverTask; | |||
private Task _keepAliveMessageSenderTask; | |||
private IMqttChannelAdapter _adapter; | |||
public MqttClient(IMqttClientAdapterFactory channelFactory, IMqttNetLogger logger) | |||
{ | |||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_packetDispatcher = new MqttPacketDispatcher(logger); | |||
} | |||
public event EventHandler<MqttClientConnectedEventArgs> Connected; | |||
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; | |||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | |||
public bool IsConnected { get; private set; } | |||
public async Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options) | |||
{ | |||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set."); | |||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); | |||
try | |||
{ | |||
_options = options; | |||
_cancellationTokenSource = new CancellationTokenSource(); | |||
_packetIdentifierProvider.Reset(); | |||
_packetDispatcher.Reset(); | |||
_adapter = _adapterFactory.CreateClientAdapter(options, _logger); | |||
_logger.Verbose<MqttClient>("Trying to connect with server."); | |||
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); | |||
_logger.Verbose<MqttClient>("Connection with server established."); | |||
await StartReceivingPacketsAsync().ConfigureAwait(false); | |||
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false); | |||
_logger.Verbose<MqttClient>("MQTT connection with server established."); | |||
_sendTracker.Restart(); | |||
if (_options.KeepAlivePeriod != TimeSpan.Zero) | |||
{ | |||
StartSendingKeepAliveMessages(); | |||
} | |||
IsConnected = true; | |||
Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent)); | |||
_logger.Info<MqttClient>("Connected."); | |||
return new MqttClientConnectResult(connectResponse.IsSessionPresent); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error<MqttClient>(exception, "Error while connecting with server."); | |||
await DisconnectInternalAsync(null, exception).ConfigureAwait(false); | |||
throw; | |||
} | |||
} | |||
public async Task DisconnectAsync() | |||
{ | |||
if (!IsConnected) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
if (!_cancellationTokenSource.IsCancellationRequested) | |||
{ | |||
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); | |||
} | |||
} | |||
finally | |||
{ | |||
await DisconnectInternalAsync(null, null).ConfigureAwait(false); | |||
} | |||
} | |||
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||
{ | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
ThrowIfNotConnected(); | |||
var subscribePacket = new MqttSubscribePacket | |||
{ | |||
PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(), | |||
TopicFilters = topicFilters.ToList() | |||
}; | |||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false); | |||
if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count) | |||
{ | |||
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1]."); | |||
} | |||
return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList(); | |||
} | |||
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters) | |||
{ | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
ThrowIfNotConnected(); | |||
var unsubscribePacket = new MqttUnsubscribePacket | |||
{ | |||
PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(), | |||
TopicFilters = topicFilters.ToList() | |||
}; | |||
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket).ConfigureAwait(false); | |||
} | |||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages) | |||
{ | |||
ThrowIfNotConnected(); | |||
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket()); | |||
var packetGroups = publishPackets.GroupBy(p => p.QualityOfServiceLevel).OrderBy(g => g.Key); | |||
foreach (var qosGroup in packetGroups) | |||
{ | |||
switch (qosGroup.Key) | |||
{ | |||
case MqttQualityOfServiceLevel.AtMostOnce: | |||
{ | |||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] | |||
await SendAsync(qosGroup.Cast<MqttBasePacket>().ToArray()).ConfigureAwait(false); | |||
break; | |||
} | |||
case MqttQualityOfServiceLevel.AtLeastOnce: | |||
{ | |||
foreach (var publishPacket in qosGroup) | |||
{ | |||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); | |||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false); | |||
} | |||
break; | |||
} | |||
case MqttQualityOfServiceLevel.ExactlyOnce: | |||
{ | |||
foreach (var publishPacket in qosGroup) | |||
{ | |||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); | |||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false); | |||
var pubRelPacket = new MqttPubRelPacket | |||
{ | |||
PacketIdentifier = pubRecPacket.PacketIdentifier | |||
}; | |||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false); | |||
} | |||
break; | |||
} | |||
default: | |||
{ | |||
throw new InvalidOperationException(); | |||
} | |||
} | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
_cancellationTokenSource?.Dispose(); | |||
_cancellationTokenSource = null; | |||
_adapter?.Dispose(); | |||
} | |||
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage) | |||
{ | |||
var connectPacket = new MqttConnectPacket | |||
{ | |||
ClientId = _options.ClientId, | |||
Username = _options.Credentials?.Username, | |||
Password = _options.Credentials?.Password, | |||
CleanSession = _options.CleanSession, | |||
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds, | |||
WillMessage = willApplicationMessage | |||
}; | |||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false); | |||
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) | |||
{ | |||
throw new MqttConnectingFailedException(response.ConnectReturnCode); | |||
} | |||
return response; | |||
} | |||
private void ThrowIfNotConnected() | |||
{ | |||
if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); | |||
} | |||
private void ThrowIfConnected(string message) | |||
{ | |||
if (IsConnected) throw new MqttProtocolViolationException(message); | |||
} | |||
private async Task DisconnectInternalAsync(Task sender, Exception exception) | |||
{ | |||
await _disconnectLock.WaitAsync(); | |||
try | |||
{ | |||
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) | |||
{ | |||
return; | |||
} | |||
_cancellationTokenSource.Cancel(false); | |||
} | |||
catch (Exception adapterException) | |||
{ | |||
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter."); | |||
} | |||
finally | |||
{ | |||
_disconnectLock.Release(); | |||
} | |||
var clientWasConnected = IsConnected; | |||
IsConnected = false; | |||
try | |||
{ | |||
if (_packetReceiverTask != null && _packetReceiverTask != sender) | |||
{ | |||
_packetReceiverTask.Wait(); | |||
} | |||
if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender) | |||
{ | |||
_keepAliveMessageSenderTask.Wait(); | |||
} | |||
if (_adapter != null) | |||
{ | |||
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); | |||
} | |||
_logger.Verbose<MqttClient>("Disconnected from adapter."); | |||
} | |||
catch (Exception adapterException) | |||
{ | |||
_logger.Warning<MqttClient>(adapterException, "Error while disconnecting from adapter."); | |||
} | |||
finally | |||
{ | |||
_adapter?.Dispose(); | |||
_adapter = null; | |||
_cancellationTokenSource?.Dispose(); | |||
_cancellationTokenSource = null; | |||
_logger.Info<MqttClient>("Disconnected."); | |||
Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected, exception)); | |||
} | |||
} | |||
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet) | |||
{ | |||
try | |||
{ | |||
if (packet is MqttPublishPacket publishPacket) | |||
{ | |||
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false); | |||
return; | |||
} | |||
if (packet is MqttPingReqPacket) | |||
{ | |||
await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false); | |||
return; | |||
} | |||
if (packet is MqttDisconnectPacket) | |||
{ | |||
await DisconnectAsync().ConfigureAwait(false); | |||
return; | |||
} | |||
if (packet is MqttPubRelPacket pubRelPacket) | |||
{ | |||
await ProcessReceivedPubRelPacket(pubRelPacket).ConfigureAwait(false); | |||
return; | |||
} | |||
_packetDispatcher.Dispatch(packet); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet."); | |||
} | |||
} | |||
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) | |||
{ | |||
try | |||
{ | |||
var applicationMessage = publishPacket.ToApplicationMessage(); | |||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage)); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message."); | |||
} | |||
} | |||
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket) | |||
{ | |||
if (_cancellationTokenSource.IsCancellationRequested) | |||
{ | |||
return Task.FromResult(0); | |||
} | |||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) | |||
{ | |||
FireApplicationMessageReceivedEvent(publishPacket); | |||
return Task.FromResult(0); | |||
} | |||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) | |||
{ | |||
FireApplicationMessageReceivedEvent(publishPacket); | |||
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); | |||
} | |||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) | |||
{ | |||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] | |||
FireApplicationMessageReceivedEvent(publishPacket); | |||
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); | |||
} | |||
throw new MqttCommunicationException("Received a not supported QoS level."); | |||
} | |||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket) | |||
{ | |||
var response = new MqttPubCompPacket | |||
{ | |||
PacketIdentifier = pubRelPacket.PacketIdentifier | |||
}; | |||
return SendAsync(response); | |||
} | |||
private Task SendAsync(params MqttBasePacket[] packets) | |||
{ | |||
_sendTracker.Restart(); | |||
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets); | |||
} | |||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket | |||
{ | |||
ushort? identifier = null; | |||
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier) | |||
{ | |||
identifier = requestPacketWithIdentifier.PacketIdentifier; | |||
} | |||
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout); | |||
await SendAsync(requestPacket).ConfigureAwait(false); | |||
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); | |||
} | |||
private async Task SendKeepAliveMessagesAsync() | |||
{ | |||
_logger.Verbose<MqttClient>("Start sending keep alive packets."); | |||
try | |||
{ | |||
while (!_cancellationTokenSource.Token.IsCancellationRequested) | |||
{ | |||
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75); | |||
if (_options.KeepAliveSendInterval.HasValue) | |||
{ | |||
keepAliveSendInterval = _options.KeepAliveSendInterval.Value; | |||
} | |||
if (_sendTracker.Elapsed > keepAliveSendInterval) | |||
{ | |||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); | |||
} | |||
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false); | |||
} | |||
} | |||
catch (Exception exception) | |||
{ | |||
if (exception is OperationCanceledException) | |||
{ | |||
} | |||
else if (exception is MqttCommunicationException) | |||
{ | |||
_logger.Warning<MqttClient>(exception, "MQTT communication exception while sending/receiving keep alive packets."); | |||
} | |||
else | |||
{ | |||
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets."); | |||
} | |||
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); | |||
} | |||
finally | |||
{ | |||
_logger.Verbose<MqttClient>("Stopped sending keep alive packets."); | |||
} | |||
} | |||
private async Task ReceivePacketsAsync() | |||
{ | |||
_logger.Verbose<MqttClient>("Start receiving packets."); | |||
try | |||
{ | |||
while (!_cancellationTokenSource.Token.IsCancellationRequested) | |||
{ | |||
_isReceivingPackets = true; | |||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); | |||
if (_cancellationTokenSource.Token.IsCancellationRequested) | |||
{ | |||
return; | |||
} | |||
StartProcessReceivedPacket(packet); | |||
} | |||
} | |||
catch (Exception exception) | |||
{ | |||
if (exception is OperationCanceledException) | |||
{ | |||
} | |||
else if (exception is MqttCommunicationException) | |||
{ | |||
_logger.Warning<MqttClient>(exception, "MQTT communication exception while receiving packets."); | |||
} | |||
else | |||
{ | |||
_logger.Error<MqttClient>(exception, "Unhandled exception while receiving packets."); | |||
} | |||
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); | |||
} | |||
finally | |||
{ | |||
_logger.Verbose<MqttClient>("Stopped receiving packets."); | |||
} | |||
} | |||
private void StartProcessReceivedPacket(MqttBasePacket packet) | |||
{ | |||
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token); | |||
} | |||
private async Task StartReceivingPacketsAsync() | |||
{ | |||
_isReceivingPackets = false; | |||
_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token); | |||
while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested) | |||
{ | |||
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false); | |||
} | |||
} | |||
private void StartSendingKeepAliveMessages() | |||
{ | |||
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token); | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientConnectResult | |||
{ |
@@ -1,6 +1,6 @@ | |||
using System; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientConnectedEventArgs : EventArgs | |||
{ |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientCredentials : IMqttClientCredentials | |||
{ |
@@ -1,14 +1,17 @@ | |||
using System; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientDisconnectedEventArgs : EventArgs | |||
{ | |||
public MqttClientDisconnectedEventArgs(bool clientWasConnected) | |||
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception) | |||
{ | |||
ClientWasConnected = clientWasConnected; | |||
Exception = exception; | |||
} | |||
public bool ClientWasConnected { get; } | |||
public Exception Exception { get; } | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Client | |||
{ | |||
public static class MqttClientExtensions | |||
{ | |||
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters) | |||
{ | |||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
return client.SubscribeAsync(topicFilters.ToList()); | |||
} | |||
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
{ | |||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); | |||
} | |||
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, string topic) | |||
{ | |||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build()); | |||
} | |||
public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters) | |||
{ | |||
if (client == null) throw new ArgumentNullException(nameof(client)); | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
return client.UnsubscribeAsync(topicFilters.ToList()); | |||
} | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
using System; | |||
using MQTTnet.Core.Serializer; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientOptions : IMqttClientOptions | |||
{ | |||
@@ -9,14 +9,13 @@ namespace MQTTnet.Core.Client | |||
public string ClientId { get; set; } = Guid.NewGuid().ToString("N"); | |||
/// <inheritdoc /> | |||
public string LogId { get; set; } | |||
public bool CleanSession { get; set; } = true; | |||
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials(); | |||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5); | |||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15); | |||
public TimeSpan? KeepAliveSendInterval { get; set; } | |||
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); | |||
@@ -1,7 +1,8 @@ | |||
using System; | |||
using MQTTnet.Core.Serializer; | |||
using System.Linq; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientOptionsBuilder | |||
{ | |||
@@ -11,15 +12,15 @@ namespace MQTTnet.Core.Client | |||
private MqttClientTlsOptions _tlsOptions; | |||
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion protocolVersion) | |||
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) | |||
{ | |||
_options.ProtocolVersion = protocolVersion; | |||
_options.ProtocolVersion = value; | |||
return this; | |||
} | |||
public MqttClientOptionsBuilder WithCommunicationTimeout(TimeSpan communicationTimeout) | |||
public MqttClientOptionsBuilder WithCommunicationTimeout(TimeSpan value) | |||
{ | |||
_options.CommunicationTimeout = communicationTimeout; | |||
_options.CommunicationTimeout = value; | |||
return this; | |||
} | |||
@@ -29,21 +30,21 @@ namespace MQTTnet.Core.Client | |||
return this; | |||
} | |||
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan keepAlivePeriod) | |||
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value) | |||
{ | |||
_options.KeepAlivePeriod = keepAlivePeriod; | |||
_options.KeepAlivePeriod = value; | |||
return this; | |||
} | |||
public MqttClientOptionsBuilder WithClientId(string clientId) | |||
public MqttClientOptionsBuilder WithClientId(string value) | |||
{ | |||
_options.ClientId = clientId; | |||
_options.ClientId = value; | |||
return this; | |||
} | |||
public MqttClientOptionsBuilder WithWillMessage(MqttApplicationMessage applicationMessage) | |||
public MqttClientOptionsBuilder WithWillMessage(MqttApplicationMessage value) | |||
{ | |||
_options.WillMessage = applicationMessage; | |||
_options.WillMessage = value; | |||
return this; | |||
} | |||
@@ -79,6 +80,24 @@ namespace MQTTnet.Core.Client | |||
return this; | |||
} | |||
public MqttClientOptionsBuilder WithTls( | |||
bool allowUntrustedCertificates = false, | |||
bool ignoreCertificateChainErrors = false, | |||
bool ignoreCertificateRevocationErrors = false, | |||
params byte[][] certificates) | |||
{ | |||
_tlsOptions = new MqttClientTlsOptions | |||
{ | |||
UseTls = true, | |||
AllowUntrustedCertificates = allowUntrustedCertificates, | |||
IgnoreCertificateChainErrors = ignoreCertificateChainErrors, | |||
IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors, | |||
Certificates = certificates?.ToList() | |||
}; | |||
return this; | |||
} | |||
public MqttClientOptionsBuilder WithTls() | |||
{ | |||
_tlsOptions = new MqttClientTlsOptions | |||
@@ -100,11 +119,11 @@ namespace MQTTnet.Core.Client | |||
if (_tcpOptions != null) | |||
{ | |||
_options.ChannelOptions = _tcpOptions; | |||
_tcpOptions.TlsOptions = _tlsOptions; | |||
} | |||
else | |||
else if (_webSocketOptions != null) | |||
{ | |||
_options.ChannelOptions = _webSocketOptions; | |||
_webSocketOptions.TlsOptions = _tlsOptions; | |||
} | |||
} | |||
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientTcpOptions : IMqttClientChannelOptions | |||
{ | |||
@@ -6,6 +6,8 @@ | |||
public int? Port { get; set; } | |||
public int BufferSize { get; set; } = 20 * 4096; | |||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using System; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public static class MqttClientTcpOptionsExtensions | |||
{ |
@@ -1,6 +1,6 @@ | |||
using System.Collections.Generic; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientTlsOptions | |||
{ |
@@ -1,7 +1,7 @@ | |||
using System.Collections.Generic; | |||
using System.Net; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttClientWebSocketOptions : IMqttClientChannelOptions | |||
{ | |||
@@ -9,7 +9,7 @@ namespace MQTTnet.Core.Client | |||
public IDictionary<string, string> RequestHeaders { get; set; } | |||
public ICollection<string> SubProtocols { get; set; } | |||
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" }; | |||
public CookieContainer CookieContainer { get; set; } | |||
@@ -0,0 +1,97 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Internal; | |||
using MQTTnet.Packets; | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttPacketDispatcher | |||
{ | |||
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>(); | |||
private readonly IMqttNetLogger _logger; | |||
public MqttPacketDispatcher(IMqttNetLogger logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout) | |||
{ | |||
var packetAwaiter = AddPacketAwaiter(responseType, identifier); | |||
try | |||
{ | |||
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false); | |||
} | |||
catch (MqttCommunicationTimedOutException) | |||
{ | |||
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name); | |||
throw; | |||
} | |||
finally | |||
{ | |||
RemovePacketAwaiter(responseType, identifier); | |||
} | |||
} | |||
public void Dispatch(MqttBasePacket packet) | |||
{ | |||
if (packet == null) throw new ArgumentNullException(nameof(packet)); | |||
var type = packet.GetType(); | |||
if (_awaiters.TryGetValue(type, out var byId)) | |||
{ | |||
ushort? identifier = 0; | |||
if (packet is IMqttPacketWithIdentifier packetWithIdentifier) | |||
{ | |||
identifier = packetWithIdentifier.PacketIdentifier; | |||
} | |||
if (byId.TryRemove(identifier.Value, out var tcs)) | |||
{ | |||
tcs.TrySetResult(packet); | |||
return; | |||
} | |||
} | |||
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched."); | |||
} | |||
public void Reset() | |||
{ | |||
_awaiters.Clear(); | |||
} | |||
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier) | |||
{ | |||
var tcs = new TaskCompletionSource<MqttBasePacket>(); | |||
if (!identifier.HasValue) | |||
{ | |||
identifier = 0; | |||
} | |||
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); | |||
if (!byId.TryAdd(identifier.Value, tcs)) | |||
{ | |||
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}."); | |||
} | |||
return tcs; | |||
} | |||
private void RemovePacketAwaiter(Type responseType, ushort? identifier) | |||
{ | |||
if (!identifier.HasValue) | |||
{ | |||
identifier = 0; | |||
} | |||
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); | |||
byId.TryRemove(identifier.Value, out var _); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} | |||
} |
@@ -1,7 +1,6 @@ | |||
using MQTTnet.Core.Packets; | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.Client | |||
namespace MQTTnet.Client | |||
{ | |||
public class MqttSubscribeResult | |||
{ |
@@ -0,0 +1,21 @@ | |||
using System; | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public interface IMqttNetLogger | |||
{ | |||
event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; | |||
void Verbose<TSource>(string message, params object[] parameters); | |||
void Info<TSource>(string message, params object[] parameters); | |||
void Warning<TSource>(Exception exception, string message, params object[] parameters); | |||
void Warning<TSource>(string message, params object[] parameters); | |||
void Error<TSource>(Exception exception, string message, params object[] parameters); | |||
void Error<TSource>(string message, params object[] parameters); | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
using System; | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public static class MqttNetGlobalLogger | |||
{ | |||
public static event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; | |||
public static bool HasListeners => LogMessagePublished != null; | |||
public static void Publish(MqttNetLogMessage logMessage) | |||
{ | |||
if (logMessage == null) throw new ArgumentNullException(nameof(logMessage)); | |||
LogMessagePublished?.Invoke(null, new MqttNetLogMessagePublishedEventArgs(logMessage)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public enum MqttNetLogLevel | |||
{ | |||
Verbose, | |||
Info, | |||
Warning, | |||
Error | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
using System; | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public sealed class MqttNetLogMessage | |||
{ | |||
public MqttNetLogMessage(string logId, DateTime timestamp, int threadId, string source, MqttNetLogLevel level, string message, Exception exception) | |||
{ | |||
LogId = logId; | |||
Timestamp = timestamp; | |||
ThreadId = threadId; | |||
Source = source; | |||
Level = level; | |||
Message = message; | |||
Exception = exception; | |||
} | |||
public string LogId { get; } | |||
public DateTime Timestamp { get; } | |||
public int ThreadId { get; } | |||
public string Source { get; } | |||
public MqttNetLogLevel Level { get; } | |||
public string Message { get; } | |||
public Exception Exception { get; } | |||
public override string ToString() | |||
{ | |||
var result = $"[{Timestamp:O}] [{LogId}] [{ThreadId}] [{Source}] [{Level}]: {Message}"; | |||
if (Exception != null) | |||
{ | |||
result += Environment.NewLine + Exception; | |||
} | |||
return result; | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
using System; | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public sealed class MqttNetLogMessagePublishedEventArgs : EventArgs | |||
{ | |||
public MqttNetLogMessagePublishedEventArgs(MqttNetLogMessage logMessage) | |||
{ | |||
TraceMessage = logMessage ?? throw new ArgumentNullException(nameof(logMessage)); | |||
} | |||
public MqttNetLogMessage TraceMessage { get; } | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
using System; | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public class MqttNetLogger : IMqttNetLogger | |||
{ | |||
private readonly string _logId; | |||
public MqttNetLogger(string logId = null) | |||
{ | |||
_logId = logId; | |||
} | |||
public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; | |||
public void Verbose<TSource>(string message, params object[] parameters) | |||
{ | |||
Publish<TSource>(MqttNetLogLevel.Verbose, null, message, parameters); | |||
} | |||
public void Info<TSource>(string message, params object[] parameters) | |||
{ | |||
Publish<TSource>(MqttNetLogLevel.Info, null, message, parameters); | |||
} | |||
public void Warning<TSource>(Exception exception, string message, params object[] parameters) | |||
{ | |||
Publish<TSource>(MqttNetLogLevel.Warning, exception, message, parameters); | |||
} | |||
public void Warning<TSource>(string message, params object[] parameters) | |||
{ | |||
Warning<TSource>(null, message, parameters); | |||
} | |||
public void Error<TSource>(Exception exception, string message, params object[] parameters) | |||
{ | |||
Publish<TSource>(MqttNetLogLevel.Error, exception, message, parameters); | |||
} | |||
public void Error<TSource>(string message, params object[] parameters) | |||
{ | |||
Warning<TSource>(null, message, parameters); | |||
} | |||
private void Publish<TSource>(MqttNetLogLevel logLevel, Exception exception, string message, object[] parameters) | |||
{ | |||
var hasLocalListeners = LogMessagePublished != null; | |||
var hasGlobalListeners = MqttNetGlobalLogger.HasListeners; | |||
if (!hasLocalListeners && !hasGlobalListeners) | |||
{ | |||
return; | |||
} | |||
if (parameters.Length > 0) | |||
{ | |||
message = string.Format(message, parameters); | |||
} | |||
var traceMessage = new MqttNetLogMessage(_logId, DateTime.Now, Environment.CurrentManagedThreadId, typeof(TSource).Name, logLevel, message, exception); | |||
if (hasGlobalListeners) | |||
{ | |||
MqttNetGlobalLogger.Publish(traceMessage); | |||
} | |||
if (hasLocalListeners) | |||
{ | |||
LogMessagePublished?.Invoke(this, new MqttNetLogMessagePublishedEventArgs(traceMessage)); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
namespace MQTTnet.Diagnostics | |||
{ | |||
public static class TargetFrameworkInfoProvider | |||
{ | |||
public static string TargetFramework | |||
{ | |||
get | |||
{ | |||
#if NET452 | |||
return "net452"; | |||
#elif NET461 | |||
return "net461"; | |||
#elif NETSTANDARD1_3 | |||
return "netstandard1.3"; | |||
#elif NETSTANDARD2_0 | |||
return "netstandard2.0"; | |||
#elif WINDOWS_UWP | |||
return "uap10.0"; | |||
#endif | |||
} | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using System; | |||
namespace MQTTnet.Core.Exceptions | |||
namespace MQTTnet.Exceptions | |||
{ | |||
public class MqttCommunicationException : Exception | |||
{ |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Exceptions | |||
namespace MQTTnet.Exceptions | |||
{ | |||
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException | |||
{ |
@@ -1,6 +1,6 @@ | |||
using System; | |||
namespace MQTTnet.Core.Exceptions | |||
namespace MQTTnet.Exceptions | |||
{ | |||
public sealed class MqttProtocolViolationException : Exception | |||
{ |
@@ -1,7 +1,7 @@ | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Core | |||
namespace MQTTnet | |||
{ | |||
public interface IApplicationMessagePublisher | |||
{ |
@@ -1,7 +1,6 @@ | |||
using System; | |||
using MQTTnet.Core.Client; | |||
namespace MQTTnet.Core | |||
namespace MQTTnet | |||
{ | |||
public interface IApplicationMessageReceiver | |||
{ |
@@ -0,0 +1,36 @@ | |||
using System; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Client; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public class MqttClientAdapterFactory : IMqttClientAdapterFactory | |||
{ | |||
public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger) | |||
{ | |||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||
var serializer = new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion }; | |||
switch (options.ChannelOptions) | |||
{ | |||
case MqttClientTcpOptions tcpOptions: | |||
{ | |||
return new MqttChannelAdapter(new MqttTcpChannel(tcpOptions), serializer, logger); | |||
} | |||
case MqttClientWebSocketOptions webSocketOptions: | |||
{ | |||
return new MqttChannelAdapter(new MqttWebSocketChannel(webSocketOptions), serializer, logger); | |||
} | |||
default: | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
#if NET451 || NETSTANDARD1_3 | |||
#else | |||
#if WINDOWS_UWP | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
@@ -9,19 +8,26 @@ using System.Threading.Tasks; | |||
using Windows.Networking; | |||
using Windows.Networking.Sockets; | |||
using Windows.Security.Cryptography.Certificates; | |||
using MQTTnet.Core.Channel; | |||
using MQTTnet.Core.Client; | |||
using MQTTnet.Channel; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable | |||
public sealed class MqttTcpChannel : IMqttChannel | |||
{ | |||
// ReSharper disable once MemberCanBePrivate.Global | |||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global | |||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. | |||
private readonly int _bufferSize = BufferSize; | |||
private readonly MqttClientTcpOptions _options; | |||
private StreamSocket _socket; | |||
public MqttTcpChannel(MqttClientTcpOptions options) | |||
{ | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_bufferSize = _options.BufferSize; | |||
} | |||
public MqttTcpChannel(StreamSocket socket) | |||
@@ -33,7 +39,6 @@ namespace MQTTnet.Implementations | |||
public Stream SendStream { get; private set; } | |||
public Stream ReceiveStream { get; private set; } | |||
public Stream RawReceiveStream { get; private set; } | |||
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; } | |||
@@ -71,24 +76,56 @@ namespace MQTTnet.Implementations | |||
public void Dispose() | |||
{ | |||
RawReceiveStream?.Dispose(); | |||
RawReceiveStream = null; | |||
SendStream?.Dispose(); | |||
SendStream = null; | |||
try | |||
{ | |||
SendStream?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
SendStream = null; | |||
} | |||
ReceiveStream?.Dispose(); | |||
ReceiveStream = null; | |||
try | |||
{ | |||
ReceiveStream?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
ReceiveStream = null; | |||
} | |||
_socket?.Dispose(); | |||
_socket = null; | |||
try | |||
{ | |||
_socket?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
_socket = null; | |||
} | |||
} | |||
private void CreateStreams() | |||
{ | |||
SendStream = _socket.OutputStream.AsStreamForWrite(); | |||
ReceiveStream = _socket.InputStream.AsStreamForRead(); | |||
RawReceiveStream = ReceiveStream; | |||
SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize); | |||
ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize); | |||
} | |||
private static Certificate LoadCertificate(MqttClientTcpOptions options) |
@@ -1,29 +1,31 @@ | |||
#if NET451 || NETSTANDARD1_3 | |||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 | |||
using System; | |||
using System.Net.Security; | |||
using System.Net.Sockets; | |||
using System.Security.Authentication; | |||
using System.Security.Cryptography.X509Certificates; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Channel; | |||
using MQTTnet.Core.Client; | |||
using System.IO; | |||
using System.Linq; | |||
using MQTTnet.Channel; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable | |||
public sealed class MqttTcpChannel : IMqttChannel | |||
{ | |||
private readonly MqttClientTcpOptions _options; | |||
//todo: this can be used with min dependency NetStandard1.6 | |||
#if NET45 | |||
#if NET452 || NET461 | |||
// ReSharper disable once MemberCanBePrivate.Global | |||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global | |||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. | |||
private readonly int _bufferSize = BufferSize; | |||
#else | |||
private readonly int _bufferSize = 0; | |||
#endif | |||
private readonly MqttClientTcpOptions _options; | |||
private Socket _socket; | |||
private SslStream _sslStream; | |||
@@ -33,6 +35,7 @@ namespace MQTTnet.Implementations | |||
public MqttTcpChannel(MqttClientTcpOptions options) | |||
{ | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
_bufferSize = options.BufferSize; | |||
} | |||
/// <summary> | |||
@@ -44,12 +47,11 @@ namespace MQTTnet.Implementations | |||
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); | |||
_sslStream = sslStream; | |||
CreateStreams(socket, sslStream); | |||
CreateStreams(); | |||
} | |||
public Stream SendStream { get; private set; } | |||
public Stream ReceiveStream { get; private set; } | |||
public Stream RawReceiveStream { get; private set; } | |||
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; } | |||
@@ -60,8 +62,7 @@ namespace MQTTnet.Implementations | |||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||
} | |||
//todo: else brach can be used with min dependency NET46 | |||
#if NET451 | |||
#if NET452 || NET461 | |||
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false); | |||
#else | |||
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); | |||
@@ -70,10 +71,10 @@ namespace MQTTnet.Implementations | |||
if (_options.TlsOptions.UseTls) | |||
{ | |||
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); | |||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); | |||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); | |||
} | |||
CreateStreams(_socket, _sslStream); | |||
CreateStreams(); | |||
} | |||
public Task DisconnectAsync() | |||
@@ -84,11 +85,70 @@ namespace MQTTnet.Implementations | |||
public void Dispose() | |||
{ | |||
_socket?.Dispose(); | |||
_socket = null; | |||
var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream); | |||
try | |||
{ | |||
SendStream?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
SendStream = null; | |||
} | |||
try | |||
{ | |||
if (!oneStreamIsUsed) | |||
{ | |||
ReceiveStream?.Dispose(); | |||
} | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
ReceiveStream = null; | |||
} | |||
try | |||
{ | |||
_sslStream?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
_sslStream = null; | |||
} | |||
_sslStream?.Dispose(); | |||
_sslStream = null; | |||
try | |||
{ | |||
_socket?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
catch (NullReferenceException) | |||
{ | |||
} | |||
finally | |||
{ | |||
_socket = null; | |||
} | |||
} | |||
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) | |||
@@ -122,40 +182,42 @@ namespace MQTTnet.Implementations | |||
return _options.TlsOptions.AllowUntrustedCertificates; | |||
} | |||
private static X509CertificateCollection LoadCertificates(MqttClientTcpOptions options) | |||
private X509CertificateCollection LoadCertificates() | |||
{ | |||
var certificates = new X509CertificateCollection(); | |||
if (options.TlsOptions.Certificates == null) | |||
if (_options.TlsOptions.Certificates == null) | |||
{ | |||
return certificates; | |||
} | |||
foreach (var certificate in options.TlsOptions.Certificates) | |||
foreach (var certificate in _options.TlsOptions.Certificates) | |||
{ | |||
certificates.Add(new X509Certificate(certificate)); | |||
certificates.Add(new X509Certificate2(certificate)); | |||
} | |||
return certificates; | |||
} | |||
private void CreateStreams(Socket socket, Stream sslStream) | |||
private void CreateStreams() | |||
{ | |||
RawReceiveStream = sslStream ?? new NetworkStream(socket); | |||
//cannot use this as default buffering prevents from receiving the first connect message | |||
//need two streams otherwise read and write have to be synchronized | |||
//todo: if branch can be used with min dependency NetStandard1.6 | |||
#if NET45 | |||
SendStream = new BufferedStream(RawReceiveStream, BufferSize); | |||
ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize); | |||
Stream stream; | |||
if (_sslStream != null) | |||
{ | |||
stream = _sslStream; | |||
} | |||
else | |||
{ | |||
stream = new NetworkStream(_socket, true); | |||
} | |||
#if NET452 || NET461 | |||
SendStream = new BufferedStream(stream, _bufferSize); | |||
ReceiveStream = new BufferedStream(stream, _bufferSize); | |||
#else | |||
SendStream = RawReceiveStream; | |||
ReceiveStream = RawReceiveStream; | |||
SendStream = stream; | |||
ReceiveStream = stream; | |||
#endif | |||
} | |||
} | |||
} | |||
#endif | |||
#endif |
@@ -1,29 +1,27 @@ | |||
#if NET451 || NETSTANDARD1_3 | |||
#else | |||
#if WINDOWS_UWP | |||
using System; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Adapter; | |||
using MQTTnet.Core.Server; | |||
using Windows.Networking.Sockets; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Serializer; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public class MqttServerAdapter : IMqttServerAdapter, IDisposable | |||
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable | |||
{ | |||
private readonly ILogger<MqttServerAdapter> _logger; | |||
private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; | |||
private readonly IMqttNetLogger _logger; | |||
private StreamSocketListener _defaultEndpointSocket; | |||
public MqttServerAdapter(ILogger<MqttServerAdapter> logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) | |||
public MqttTcpServerAdapter(IMqttNetLogger logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); | |||
} | |||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; | |||
public async Task StartAsync(MqttServerOptions options) | |||
public async Task StartAsync(IMqttServerOptions options) | |||
{ | |||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||
@@ -32,7 +30,6 @@ namespace MQTTnet.Implementations | |||
if (options.DefaultEndpointOptions.IsEnabled) | |||
{ | |||
_defaultEndpointSocket = new StreamSocketListener(); | |||
_defaultEndpointSocket.Control.NoDelay = true; | |||
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket); | |||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; | |||
} | |||
@@ -45,6 +42,11 @@ namespace MQTTnet.Implementations | |||
public Task StopAsync() | |||
{ | |||
if (_defaultEndpointSocket != null) | |||
{ | |||
_defaultEndpointSocket.ConnectionReceived -= AcceptDefaultEndpointConnectionsAsync; | |||
} | |||
_defaultEndpointSocket?.Dispose(); | |||
_defaultEndpointSocket = null; | |||
@@ -60,14 +62,12 @@ namespace MQTTnet.Implementations | |||
{ | |||
try | |||
{ | |||
args.Socket.Control.NoDelay = true; | |||
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(args.Socket)); | |||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger); | |||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint."); | |||
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint."); | |||
} | |||
} | |||
} |
@@ -1,5 +1,4 @@ | |||
#if NET451 || NETSTANDARD1_3 | |||
#if NET452 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0 | |||
using System; | |||
using System.Net; | |||
using System.Net.Security; | |||
@@ -8,31 +7,30 @@ using System.Security.Authentication; | |||
using System.Security.Cryptography.X509Certificates; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Adapter; | |||
using MQTTnet.Core.Server; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Serializer; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public class MqttServerAdapter : IMqttServerAdapter, IDisposable | |||
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable | |||
{ | |||
private readonly ILogger<MqttServerAdapter> _logger; | |||
private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory; | |||
private readonly IMqttNetLogger _logger; | |||
private CancellationTokenSource _cancellationTokenSource; | |||
private Socket _defaultEndpointSocket; | |||
private Socket _tlsEndpointSocket; | |||
private X509Certificate2 _tlsCertificate; | |||
public MqttServerAdapter(ILogger<MqttServerAdapter> logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory) | |||
public MqttTcpServerAdapter(IMqttNetLogger logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory)); | |||
} | |||
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; | |||
public Task StartAsync(MqttServerOptions options) | |||
public Task StartAsync(IMqttServerOptions options) | |||
{ | |||
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started."); | |||
@@ -41,10 +39,10 @@ namespace MQTTnet.Implementations | |||
if (options.DefaultEndpointOptions.IsEnabled) | |||
{ | |||
_defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||
_defaultEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetDefaultEndpointPort())); | |||
_defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); | |||
_defaultEndpointSocket.Listen(options.ConnectionBacklog); | |||
Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); | |||
Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false); | |||
} | |||
if (options.TlsEndpointOptions.IsEnabled) | |||
@@ -61,10 +59,10 @@ namespace MQTTnet.Implementations | |||
} | |||
_tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); | |||
_tlsEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetTlsEndpointPort())); | |||
_tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort())); | |||
_tlsEndpointSocket.Listen(options.ConnectionBacklog); | |||
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); | |||
Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false); | |||
} | |||
return Task.FromResult(0); | |||
@@ -99,19 +97,27 @@ namespace MQTTnet.Implementations | |||
try | |||
{ | |||
//todo: else branch can be used with min dependency NET46 | |||
#if NET451 | |||
#if NET452 || NET461 | |||
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false); | |||
#else | |||
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); | |||
#endif | |||
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, null)); | |||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); | |||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint."); | |||
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted) | |||
{ | |||
return; | |||
} | |||
//excessive CPU consumed if in endless loop of socket errors | |||
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at default endpoint."); | |||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); | |||
} | |||
} | |||
@@ -123,7 +129,7 @@ namespace MQTTnet.Implementations | |||
{ | |||
try | |||
{ | |||
#if NET451 | |||
#if NET452 || NET461 | |||
var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false); | |||
#else | |||
var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false); | |||
@@ -131,15 +137,22 @@ namespace MQTTnet.Implementations | |||
var sslStream = new SslStream(new NetworkStream(clientSocket)); | |||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); | |||
var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream)); | |||
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); | |||
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(new EventId(), exception, "Error while accepting connection at TLS endpoint."); | |||
if (exception is SocketException s && s.SocketErrorCode == SocketError.OperationAborted) | |||
{ | |||
return; | |||
} | |||
//excessive CPU consumed if in endless loop of socket errors | |||
_logger.Error<MqttTcpServerAdapter>(exception, "Error while accepting connection at TLS endpoint."); | |||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); | |||
} | |||
} |
@@ -1,16 +1,20 @@ | |||
using MQTTnet.Core.Channel; | |||
using MQTTnet.Core.Client; | |||
using System; | |||
using System; | |||
using System.IO; | |||
using System.Net.WebSockets; | |||
using System.Security.Cryptography.X509Certificates; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Channel; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable | |||
public sealed class MqttWebSocketChannel : IMqttChannel | |||
{ | |||
// ReSharper disable once MemberCanBePrivate.Global | |||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global | |||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. | |||
private readonly MqttClientWebSocketOptions _options; | |||
private ClientWebSocket _webSocket; | |||
@@ -19,20 +23,26 @@ namespace MQTTnet.Implementations | |||
_options = options ?? throw new ArgumentNullException(nameof(options)); | |||
} | |||
public Stream SendStream => RawReceiveStream; | |||
public Stream ReceiveStream => RawReceiveStream; | |||
public Stream RawReceiveStream { get; private set; } | |||
public Stream SendStream { get; private set; } | |||
public Stream ReceiveStream { get; private set; } | |||
public async Task ConnectAsync() | |||
{ | |||
var uri = _options.Uri; | |||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase)) | |||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) | |||
{ | |||
uri = "ws://" + uri; | |||
if (_options.TlsOptions?.UseTls == false) | |||
{ | |||
uri = "ws://" + uri; | |||
} | |||
else | |||
{ | |||
uri = "wss://" + uri; | |||
} | |||
} | |||
_webSocket = new ClientWebSocket(); | |||
if (_options.RequestHeaders != null) | |||
{ | |||
foreach (var requestHeader in _options.RequestHeaders) | |||
@@ -63,14 +73,14 @@ namespace MQTTnet.Implementations | |||
} | |||
} | |||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None); | |||
RawReceiveStream = new WebSocketStream(_webSocket); | |||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false); | |||
SendStream = new WebSocketStream(_webSocket); | |||
ReceiveStream = SendStream; | |||
} | |||
public async Task DisconnectAsync() | |||
{ | |||
RawReceiveStream = null; | |||
if (_webSocket == null) | |||
{ | |||
return; | |||
@@ -80,12 +90,23 @@ namespace MQTTnet.Implementations | |||
{ | |||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); | |||
} | |||
Dispose(); | |||
} | |||
public void Dispose() | |||
{ | |||
_webSocket?.Dispose(); | |||
_webSocket = null; | |||
try | |||
{ | |||
_webSocket?.Dispose(); | |||
} | |||
catch (ObjectDisposedException) | |||
{ | |||
} | |||
finally | |||
{ | |||
_webSocket = null; | |||
} | |||
} | |||
} | |||
} |
@@ -1,16 +1,20 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Net.WebSockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Exceptions; | |||
using MQTTnet.Exceptions; | |||
namespace MQTTnet.Implementations | |||
{ | |||
public class WebSocketStream : Stream | |||
{ | |||
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize]; | |||
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize); | |||
private readonly WebSocket _webSocket; | |||
public WebSocketStream(WebSocket webSocket) | |||
{ | |||
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket)); | |||
@@ -34,43 +38,69 @@ namespace MQTTnet.Implementations | |||
{ | |||
} | |||
public override Task FlushAsync(CancellationToken cancellationToken) | |||
{ | |||
return Task.FromResult(0); | |||
} | |||
public override int Read(byte[] buffer, int offset, int count) | |||
{ | |||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); | |||
} | |||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | |||
{ | |||
var currentOffset = offset; | |||
var targetOffset = offset + count; | |||
while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset) | |||
var bytesRead = 0; | |||
// Use existing date from buffer. | |||
while (count > 0 && _buffer.Any()) | |||
{ | |||
buffer[offset] = _buffer.Dequeue(); | |||
count--; | |||
bytesRead++; | |||
offset++; | |||
} | |||
if (count == 0) | |||
{ | |||
return bytesRead; | |||
} | |||
// Fetch new data if the buffer is not full. | |||
while (_webSocket.State == WebSocketState.Open) | |||
{ | |||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false); | |||
currentOffset += response.Count; | |||
count -= response.Count; | |||
await FetchChunkAsync(cancellationToken).ConfigureAwait(false); | |||
if (response.MessageType == WebSocketMessageType.Close) | |||
while (count > 0 && _buffer.Any()) | |||
{ | |||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); | |||
buffer[offset] = _buffer.Dequeue(); | |||
count--; | |||
bytesRead++; | |||
offset++; | |||
} | |||
if (count == 0) | |||
{ | |||
return bytesRead; | |||
} | |||
} | |||
if (_webSocket.State == WebSocketState.Closed) | |||
{ | |||
throw new MqttCommunicationException( "connection closed" ); | |||
throw new MqttCommunicationException("WebSocket connection closed."); | |||
} | |||
return currentOffset - offset; | |||
return bytesRead; | |||
} | |||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | |||
{ | |||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken); | |||
} | |||
public override int Read(byte[] buffer, int offset, int count) | |||
public override void Write(byte[] buffer, int offset, int count) | |||
{ | |||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); | |||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); | |||
} | |||
public override void Write(byte[] buffer, int offset, int count) | |||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | |||
{ | |||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); | |||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken); | |||
} | |||
public override long Seek(long offset, SeekOrigin origin) | |||
@@ -82,5 +112,26 @@ namespace MQTTnet.Implementations | |||
{ | |||
throw new NotSupportedException(); | |||
} | |||
private async Task FetchChunkAsync(CancellationToken cancellationToken) | |||
{ | |||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false); | |||
if (response.MessageType == WebSocketMessageType.Close) | |||
{ | |||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false); | |||
} | |||
else if (response.MessageType == WebSocketMessageType.Binary) | |||
{ | |||
for (var i = 0; i < response.Count; i++) | |||
{ | |||
_buffer.Enqueue(_chunckBuffer[i]); | |||
} | |||
} | |||
else if (response.MessageType == WebSocketMessageType.Text) | |||
{ | |||
throw new MqttProtocolViolationException("WebSocket channel received TEXT message."); | |||
} | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using MQTTnet.Core.Packets; | |||
using MQTTnet.Packets; | |||
namespace MQTTnet.Core.Internal | |||
namespace MQTTnet.Internal | |||
{ | |||
internal static class MqttApplicationMessageExtensions | |||
{ |
@@ -1,21 +1,23 @@ | |||
using System; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Exceptions; | |||
using MQTTnet.Exceptions; | |||
namespace MQTTnet.Core.Internal | |||
namespace MQTTnet.Internal | |||
{ | |||
public static class TaskExtensions | |||
{ | |||
public static async Task TimeoutAfter(this Task task, TimeSpan timeout) | |||
{ | |||
using (var cancellationTokenSource = new CancellationTokenSource()) | |||
if (task == null) throw new ArgumentNullException(nameof(task)); | |||
using (var timeoutCts = new CancellationTokenSource()) | |||
{ | |||
try | |||
{ | |||
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token); | |||
var timeoutTask = Task.Delay(timeout, timeoutCts.Token); | |||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false); | |||
if (finishedTask == timeoutTask) | |||
{ | |||
throw new MqttCommunicationTimedOutException(); | |||
@@ -28,23 +30,25 @@ namespace MQTTnet.Core.Internal | |||
if (task.IsFaulted) | |||
{ | |||
throw new MqttCommunicationException(task.Exception.GetBaseException()); | |||
throw new MqttCommunicationException(task.Exception?.GetBaseException()); | |||
} | |||
} | |||
finally | |||
{ | |||
cancellationTokenSource.Cancel(); | |||
timeoutCts.Cancel(); | |||
} | |||
} | |||
} | |||
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) | |||
{ | |||
using (var cancellationTokenSource = new CancellationTokenSource()) | |||
if (task == null) throw new ArgumentNullException(nameof(task)); | |||
using (var timeoutCts = new CancellationTokenSource()) | |||
{ | |||
try | |||
{ | |||
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token); | |||
var timeoutTask = Task.Delay(timeout, timeoutCts.Token); | |||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false); | |||
if (finishedTask == timeoutTask) | |||
@@ -66,7 +70,7 @@ namespace MQTTnet.Core.Internal | |||
} | |||
finally | |||
{ | |||
cancellationTokenSource.Cancel(); | |||
timeoutCts.Cancel(); | |||
} | |||
} | |||
} |
@@ -1,54 +1,65 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFrameworks>netstandard1.3;net451;uap10.0</TargetFrameworks> | |||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">netstandard1.3;netstandard2.0;net452;net461;uap10.0</TargetFrameworks> | |||
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks> | |||
<AssemblyName>MQTTnet</AssemblyName> | |||
<RootNamespace>MQTTnet</RootNamespace> | |||
<AssemblyVersion>2.5.0.0</AssemblyVersion> | |||
<FileVersion>2.5.0.0</FileVersion> | |||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> | |||
<DebugType>Full</DebugType> | |||
<AssemblyVersion>0.0.0.0</AssemblyVersion> | |||
<FileVersion>0.0.0.0</FileVersion> | |||
<Version>0.0.0.0</Version> | |||
<Company /> | |||
<Product /> | |||
<Description /> | |||
<Authors /> | |||
<PackageId /> | |||
<SignAssembly>false</SignAssembly> | |||
<DelaySign>false</DelaySign> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> | |||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> | |||
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> | |||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> | |||
<TargetPlatformVersion>10.0.15063.0</TargetPlatformVersion> | |||
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion> | |||
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion> | |||
<TargetFrameworkIdentifier>.NETCore</TargetFrameworkIdentifier> | |||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion> | |||
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants> | |||
<DefaultLanguage>en</DefaultLanguage> | |||
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" /> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" /> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" /> | |||
</ItemGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'"> | |||
<DefineConstants>RELEASE;NETSTANDARD1_3</DefineConstants> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" /> | |||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'"> | |||
<PackageReference Include="System.Net.Security" Version="4.3.2" /> | |||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> | |||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" /> | |||
</ItemGroup> | |||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'"> | |||
<PackageReference Include="System.Net.Security" Version="4.3.2" /> | |||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> | |||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" /> | |||
<PackageReference Include="System.Threading.Thread" Version="4.3.0" /> | |||
</ItemGroup> | |||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'"> | |||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.0" /> | |||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" /> | |||
</ItemGroup> | |||
<ItemGroup Condition="'$(TargetFramework)'=='net452'"> | |||
</ItemGroup> | |||
<ItemGroup Condition="'$(TargetFramework)'=='net461'"> | |||
</ItemGroup> | |||
</Project> |
@@ -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; | |||
} | |||
} |
@@ -1,21 +1,24 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Client; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher | |||
public interface IManagedMqttClient : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable | |||
{ | |||
bool IsStarted { get; } | |||
bool IsConnected { get; } | |||
event EventHandler<MqttClientConnectedEventArgs> Connected; | |||
event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; | |||
event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed; | |||
Task StartAsync(IManagedMqttClientOptions options); | |||
Task StopAsync(); | |||
Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters); | |||
Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters); | |||
Task UnsubscribeAsync(IEnumerable<string> topics); | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
using System; | |||
using MQTTnet.Core.Client; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public interface IManagedMqttClientOptions | |||
{ |
@@ -1,7 +1,7 @@ | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public interface IManagedMqttClientStorage | |||
{ |
@@ -4,29 +4,32 @@ using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Core.Client; | |||
using MQTTnet.Core.Exceptions; | |||
using MQTTnet.Core.Protocol; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Client; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public class ManagedMqttClient : IManagedMqttClient | |||
{ | |||
private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager(); | |||
private readonly BlockingCollection<MqttApplicationMessage> _messageQueue = new BlockingCollection<MqttApplicationMessage>(); | |||
private readonly HashSet<TopicFilter> _subscriptions = new HashSet<TopicFilter>(); | |||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>(); | |||
private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1); | |||
private readonly List<string> _unsubscriptions = new List<string>(); | |||
private readonly IMqttClient _mqttClient; | |||
private readonly ILogger<ManagedMqttClient> _logger; | |||
private readonly IMqttNetLogger _logger; | |||
private CancellationTokenSource _connectionCancellationToken; | |||
private CancellationTokenSource _publishingCancellationToken; | |||
private ManagedMqttClientStorageManager _storageManager; | |||
private IManagedMqttClientOptions _options; | |||
private bool _subscriptionsNotPushed; | |||
public ManagedMqttClient(ILogger<ManagedMqttClient> logger, IMqttClient mqttClient) | |||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); | |||
@@ -37,10 +40,13 @@ namespace MQTTnet.Core.ManagedClient | |||
} | |||
public bool IsConnected => _mqttClient.IsConnected; | |||
public bool IsStarted => _connectionCancellationToken != null; | |||
public event EventHandler<MqttClientConnectedEventArgs> Connected; | |||
public event EventHandler<MqttClientDisconnectedEventArgs> Disconnected; | |||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; | |||
public event EventHandler<ApplicationMessageProcessedEventArgs> ApplicationMessageProcessed; | |||
public event EventHandler SynchronizingSubscriptionsFailed; | |||
public async Task StartAsync(IManagedMqttClientOptions options) | |||
{ | |||
@@ -52,36 +58,29 @@ namespace MQTTnet.Core.ManagedClient | |||
throw new NotSupportedException("The managed client does not support existing sessions."); | |||
} | |||
if (_connectionCancellationToken != null) | |||
{ | |||
throw new InvalidOperationException("The managed client is already started."); | |||
} | |||
if (_connectionCancellationToken != null) throw new InvalidOperationException("The managed client is already started."); | |||
_options = options; | |||
await _storageManager.SetStorageAsync(_options.Storage).ConfigureAwait(false); | |||
if (_options.Storage != null) | |||
{ | |||
var loadedMessages = await _options.Storage.LoadQueuedMessagesAsync().ConfigureAwait(false); | |||
foreach (var loadedMessage in loadedMessages) | |||
{ | |||
_messageQueue.Add(loadedMessage); | |||
} | |||
_storageManager = new ManagedMqttClientStorageManager(_options.Storage); | |||
await _storageManager.LoadQueuedMessagesAsync().ConfigureAwait(false); | |||
} | |||
_connectionCancellationToken = new CancellationTokenSource(); | |||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed | |||
Task.Factory.StartNew(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); | |||
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false); | |||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed | |||
_logger.LogInformation("Started"); | |||
_logger.Info<ManagedMqttClient>("Started"); | |||
} | |||
public Task StopAsync() | |||
{ | |||
_connectionCancellationToken?.Cancel(false); | |||
_connectionCancellationToken = null; | |||
StopPublishing(); | |||
StopMaintainingConnection(); | |||
while (_messageQueue.Any()) | |||
{ | |||
@@ -97,43 +96,60 @@ namespace MQTTnet.Core.ManagedClient | |||
foreach (var applicationMessage in applicationMessages) | |||
{ | |||
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); | |||
if (_storageManager != null) | |||
{ | |||
await _storageManager.AddAsync(applicationMessage).ConfigureAwait(false); | |||
} | |||
_messageQueue.Add(applicationMessage); | |||
} | |||
} | |||
public Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||
public async Task SubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||
{ | |||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); | |||
lock (_subscriptions) | |||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
foreach (var topicFilter in topicFilters) | |||
{ | |||
if (_subscriptions.Add(topicFilter)) | |||
{ | |||
_subscriptionsNotPushed = true; | |||
} | |||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel; | |||
_subscriptionsNotPushed = true; | |||
} | |||
} | |||
return Task.FromResult(0); | |||
finally | |||
{ | |||
_subscriptionsSemaphore.Release(); | |||
} | |||
} | |||
public Task UnsubscribeAsync(IEnumerable<TopicFilter> topicFilters) | |||
public async Task UnsubscribeAsync(IEnumerable<string> topics) | |||
{ | |||
lock (_subscriptions) | |||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
foreach (var topicFilter in topicFilters) | |||
foreach (var topic in topics) | |||
{ | |||
if (_subscriptions.Remove(topicFilter)) | |||
if (_subscriptions.Remove(topic)) | |||
{ | |||
_unsubscriptions.Add(topic); | |||
_subscriptionsNotPushed = true; | |||
} | |||
} | |||
} | |||
finally | |||
{ | |||
_subscriptionsSemaphore.Release(); | |||
} | |||
} | |||
return Task.FromResult(0); | |||
public void Dispose() | |||
{ | |||
_messageQueue?.Dispose(); | |||
_subscriptionsSemaphore?.Dispose(); | |||
_connectionCancellationToken?.Dispose(); | |||
_publishingCancellationToken?.Dispose(); | |||
} | |||
private async Task MaintainConnectionAsync(CancellationToken cancellationToken) | |||
@@ -142,33 +158,48 @@ namespace MQTTnet.Core.ManagedClient | |||
{ | |||
while (!cancellationToken.IsCancellationRequested) | |||
{ | |||
var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false); | |||
if (connectionState == ReconnectionResult.NotConnected) | |||
{ | |||
_publishingCancellationToken?.Cancel(false); | |||
_publishingCancellationToken = null; | |||
await TryMaintainConnectionAsync(cancellationToken).ConfigureAwait(false); | |||
} | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection."); | |||
} | |||
finally | |||
{ | |||
await _mqttClient.DisconnectAsync().ConfigureAwait(false); | |||
_logger.Info<ManagedMqttClient>("Stopped"); | |||
} | |||
} | |||
await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false); | |||
continue; | |||
} | |||
private async Task TryMaintainConnectionAsync(CancellationToken cancellationToken) | |||
{ | |||
try | |||
{ | |||
var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false); | |||
if (connectionState == ReconnectionResult.NotConnected) | |||
{ | |||
StopPublishing(); | |||
await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false); | |||
return; | |||
} | |||
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) | |||
{ | |||
await PushSubscriptionsAsync(); | |||
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) | |||
{ | |||
await SynchronizeSubscriptionsAsync().ConfigureAwait(false); | |||
_publishingCancellationToken = new CancellationTokenSource(); | |||
StartPublishing(); | |||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed | |||
Task.Factory.StartNew(() => PublishQueuedMessagesAsync(_publishingCancellationToken.Token), _publishingCancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); | |||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed | |||
continue; | |||
} | |||
return; | |||
} | |||
if (connectionState == ReconnectionResult.StillConnected) | |||
{ | |||
await Task.Delay(100, _connectionCancellationToken.Token).ConfigureAwait(false); // Consider using the _Disconnected_ event here. (TaskCompletionSource) | |||
} | |||
if (connectionState == ReconnectionResult.StillConnected) | |||
{ | |||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); | |||
} | |||
} | |||
catch (OperationCanceledException) | |||
@@ -176,16 +207,11 @@ namespace MQTTnet.Core.ManagedClient | |||
} | |||
catch (MqttCommunicationException exception) | |||
{ | |||
_logger.LogWarning(new EventId(), exception, "Communication exception while maintaining connection."); | |||
_logger.Warning<ManagedMqttClient>(exception, "Communication exception while maintaining connection."); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(new EventId(), exception, "Unhandled exception while maintaining connection."); | |||
} | |||
finally | |||
{ | |||
await _mqttClient.DisconnectAsync().ConfigureAwait(false); | |||
_logger.LogInformation("Stopped"); | |||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while maintaining connection."); | |||
} | |||
} | |||
@@ -207,27 +233,38 @@ namespace MQTTnet.Core.ManagedClient | |||
} | |||
await TryPublishQueuedMessageAsync(message).ConfigureAwait(false); | |||
await _storageManager.RemoveAsync(message).ConfigureAwait(false); | |||
} | |||
} | |||
catch (OperationCanceledException) | |||
{ | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application messages."); | |||
} | |||
finally | |||
{ | |||
_logger.LogInformation("Stopped publishing messages"); | |||
_logger.Verbose<ManagedMqttClient>("Stopped publishing messages."); | |||
} | |||
} | |||
private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message) | |||
{ | |||
Exception transmitException = null; | |||
try | |||
{ | |||
await _mqttClient.PublishAsync(message).ConfigureAwait(false); | |||
if (_storageManager != null) | |||
{ | |||
await _storageManager.RemoveAsync(message).ConfigureAwait(false); | |||
} | |||
} | |||
catch (MqttCommunicationException exception) | |||
{ | |||
_logger.LogWarning(new EventId(), exception, "Publishing application message failed."); | |||
transmitException = exception; | |||
_logger.Warning<ManagedMqttClient>(exception, "Publishing application message failed."); | |||
if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) | |||
{ | |||
@@ -236,34 +273,60 @@ namespace MQTTnet.Core.ManagedClient | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(new EventId(), exception, "Unhandled exception while publishing queued application message."); | |||
transmitException = exception; | |||
_logger.Error<ManagedMqttClient>(exception, "Unhandled exception while publishing queued application message."); | |||
} | |||
finally | |||
{ | |||
ApplicationMessageProcessed?.Invoke(this, new ApplicationMessageProcessedEventArgs(message, transmitException)); | |||
} | |||
} | |||
private async Task PushSubscriptionsAsync() | |||
private async Task SynchronizeSubscriptionsAsync() | |||
{ | |||
_logger.LogInformation(nameof(ManagedMqttClient), "Synchronizing subscriptions"); | |||
_logger.Info<ManagedMqttClient>(nameof(ManagedMqttClient), "Synchronizing subscriptions"); | |||
List<TopicFilter> subscriptions; | |||
lock (_subscriptions) | |||
List<string> unsubscriptions; | |||
await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
subscriptions = _subscriptions.ToList(); | |||
subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList(); | |||
unsubscriptions = new List<string>(_unsubscriptions); | |||
_unsubscriptions.Clear(); | |||
_subscriptionsNotPushed = false; | |||
} | |||
if (!_subscriptions.Any()) | |||
finally | |||
{ | |||
_subscriptionsSemaphore.Release(); | |||
} | |||
if (!subscriptions.Any() && !unsubscriptions.Any()) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false); | |||
if (subscriptions.Any()) | |||
{ | |||
await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false); | |||
} | |||
if (unsubscriptions.Any()) | |||
{ | |||
await _mqttClient.UnsubscribeAsync(unsubscriptions).ConfigureAwait(false); | |||
} | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogWarning(new EventId(), exception, "Synchronizing subscriptions failed"); | |||
_logger.Warning<ManagedMqttClient>(exception, "Synchronizing subscriptions failed."); | |||
_subscriptionsNotPushed = true; | |||
SynchronizingSubscriptionsFailed?.Invoke(this, EventArgs.Empty); | |||
} | |||
} | |||
@@ -299,5 +362,35 @@ namespace MQTTnet.Core.ManagedClient | |||
{ | |||
Connected?.Invoke(this, eventArgs); | |||
} | |||
private void StartPublishing() | |||
{ | |||
if (_publishingCancellationToken != null) | |||
{ | |||
StopPublishing(); | |||
} | |||
var cts = new CancellationTokenSource(); | |||
_publishingCancellationToken = cts; | |||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed | |||
Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false); | |||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed | |||
} | |||
private void StopPublishing() | |||
{ | |||
_publishingCancellationToken?.Cancel(false); | |||
_publishingCancellationToken?.Dispose(); | |||
_publishingCancellationToken = null; | |||
} | |||
private void StopMaintainingConnection() | |||
{ | |||
_connectionCancellationToken?.Cancel(false); | |||
_connectionCancellationToken?.Dispose(); | |||
_connectionCancellationToken = null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public static class ManagedMqttClientExtensions | |||
{ | |||
public static Task SubscribeAsync(this IManagedMqttClient managedClient, params TopicFilter[] topicFilters) | |||
{ | |||
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); | |||
return managedClient.SubscribeAsync(topicFilters); | |||
} | |||
public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel) | |||
{ | |||
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); | |||
} | |||
public static Task SubscribeAsync(this IManagedMqttClient managedClient, string topic) | |||
{ | |||
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); | |||
if (topic == null) throw new ArgumentNullException(nameof(topic)); | |||
return managedClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build()); | |||
} | |||
public static Task UnsubscribeAsync(this IManagedMqttClient managedClient, params string[] topicFilters) | |||
{ | |||
if (managedClient == null) throw new ArgumentNullException(nameof(managedClient)); | |||
return managedClient.UnsubscribeAsync(topicFilters); | |||
} | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
using System; | |||
using MQTTnet.Core.Client; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public class ManagedMqttClientOptions : IManagedMqttClientOptions | |||
{ |
@@ -0,0 +1,74 @@ | |||
using System; | |||
using MQTTnet.Client; | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public class ManagedMqttClientOptionsBuilder | |||
{ | |||
private readonly ManagedMqttClientOptions _options = new ManagedMqttClientOptions(); | |||
private MqttClientOptionsBuilder _clientOptionsBuilder; | |||
public ManagedMqttClientOptionsBuilder WithAutoReconnectDelay(TimeSpan value) | |||
{ | |||
_options.AutoReconnectDelay = value; | |||
return this; | |||
} | |||
public ManagedMqttClientOptionsBuilder WithStorage(IManagedMqttClientStorage value) | |||
{ | |||
_options.Storage = value; | |||
return this; | |||
} | |||
public ManagedMqttClientOptionsBuilder WithClientOptions(IMqttClientOptions value) | |||
{ | |||
if (_clientOptionsBuilder != null) | |||
{ | |||
throw new InvalidOperationException("Cannot use client options builder and client options at the same time."); | |||
} | |||
_options.ClientOptions = value ?? throw new ArgumentNullException(nameof(value)); | |||
return this; | |||
} | |||
public ManagedMqttClientOptionsBuilder WithClientOptions(MqttClientOptionsBuilder builder) | |||
{ | |||
if (_options.ClientOptions != null) | |||
{ | |||
throw new InvalidOperationException("Cannot use client options builder and client options at the same time."); | |||
} | |||
_clientOptionsBuilder = builder; | |||
return this; | |||
} | |||
public ManagedMqttClientOptionsBuilder WithClientOptions(Action<MqttClientOptionsBuilder> options) | |||
{ | |||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||
if (_clientOptionsBuilder == null) | |||
{ | |||
_clientOptionsBuilder = new MqttClientOptionsBuilder(); | |||
} | |||
options(_clientOptionsBuilder); | |||
return this; | |||
} | |||
public ManagedMqttClientOptions Build() | |||
{ | |||
if (_clientOptionsBuilder != null) | |||
{ | |||
_options.ClientOptions = _clientOptionsBuilder.Build(); | |||
} | |||
if (_options.ClientOptions == null) | |||
{ | |||
throw new InvalidOperationException("The ClientOptions cannot be null."); | |||
} | |||
return _options; | |||
} | |||
} | |||
} |
@@ -1,25 +1,27 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public class ManagedMqttClientStorageManager | |||
{ | |||
private readonly List<MqttApplicationMessage> _applicationMessages = new List<MqttApplicationMessage>(); | |||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); | |||
private IManagedMqttClientStorage _storage; | |||
private readonly IManagedMqttClientStorage _storage; | |||
public async Task SetStorageAsync(IManagedMqttClientStorage storage) | |||
public ManagedMqttClientStorageManager(IManagedMqttClientStorage storage) | |||
{ | |||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
_storage = storage; | |||
} | |||
finally | |||
_storage = storage ?? throw new ArgumentNullException(nameof(storage)); | |||
} | |||
public async Task LoadQueuedMessagesAsync() | |||
{ | |||
var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false); | |||
foreach (var loadedMessage in loadedMessages) | |||
{ | |||
_semaphore.Release(); | |||
_applicationMessages.Add(loadedMessage); | |||
} | |||
} | |||
@@ -28,11 +30,6 @@ namespace MQTTnet.Core.ManagedClient | |||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
if (_storage == null) | |||
{ | |||
return; | |||
} | |||
_applicationMessages.Add(applicationMessage); | |||
await SaveAsync().ConfigureAwait(false); | |||
} | |||
@@ -47,11 +44,6 @@ namespace MQTTnet.Core.ManagedClient | |||
await _semaphore.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
if (_storage == null) | |||
{ | |||
return; | |||
} | |||
var index = _applicationMessages.IndexOf(applicationMessage); | |||
if (index == -1) | |||
{ |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.ManagedClient | |||
namespace MQTTnet.ManagedClient | |||
{ | |||
public enum ReconnectionResult | |||
{ |
@@ -1,7 +1,7 @@ | |||
using System; | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core | |||
namespace MQTTnet | |||
{ | |||
public sealed class MqttApplicationMessage | |||
{ |
@@ -2,10 +2,10 @@ | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using MQTTnet.Core.Exceptions; | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Exceptions; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core | |||
namespace MQTTnet | |||
{ | |||
public class MqttApplicationMessageBuilder | |||
{ |
@@ -0,0 +1,25 @@ | |||
using System; | |||
using System.Text; | |||
namespace MQTTnet | |||
{ | |||
public static class MqttApplicationMessageExtensions | |||
{ | |||
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage) | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
if (applicationMessage.Payload == null) | |||
{ | |||
return null; | |||
} | |||
if (applicationMessage.Payload.Length == 0) | |||
{ | |||
return string.Empty; | |||
} | |||
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length); | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using System; | |||
namespace MQTTnet.Core | |||
namespace MQTTnet | |||
{ | |||
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs | |||
{ |
@@ -1,130 +1,59 @@ | |||
using System; | |||
using MQTTnet.Core.Adapter; | |||
using MQTTnet.Core.Client; | |||
using MQTTnet.Core.Serializer; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Options; | |||
using System.Collections.Generic; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Client; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Implementations; | |||
using MQTTnet.Core.ManagedClient; | |||
using MQTTnet.Core.Server; | |||
using MQTTnet.Core.Channel; | |||
using MQTTnet.ManagedClient; | |||
using MQTTnet.Server; | |||
namespace MQTTnet | |||
{ | |||
public class MqttFactory : IMqttCommunicationAdapterFactory, IMqttClientSesssionFactory, IMqttClientFactory, IMqttServerFactory | |||
public class MqttFactory : IMqttClientFactory, IMqttServerFactory | |||
{ | |||
private readonly IServiceProvider _serviceProvider; | |||
private static IServiceProvider BuildServiceProvider() | |||
{ | |||
var serviceProvider = new ServiceCollection() | |||
.AddMqttClient() | |||
.AddMqttServer() | |||
.AddLogging() | |||
.BuildServiceProvider(); | |||
serviceProvider.GetRequiredService<ILoggerFactory>() | |||
.AddMqttTrace(); | |||
return serviceProvider; | |||
} | |||
public MqttFactory() | |||
: this(BuildServiceProvider()) | |||
{ | |||
} | |||
public MqttFactory(IServiceProvider serviceProvider) | |||
{ | |||
_serviceProvider = serviceProvider; | |||
} | |||
public ILoggerFactory GetLoggerFactory() | |||
{ | |||
return _serviceProvider.GetRequiredService<ILoggerFactory>(); | |||
} | |||
public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options) | |||
{ | |||
var logger = _serviceProvider.GetRequiredService<ILogger<MqttChannelCommunicationAdapter>>(); | |||
return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options.ChannelOptions), CreateSerializer(options.ProtocolVersion), logger); | |||
} | |||
public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel) | |||
public IMqttClient CreateMqttClient() | |||
{ | |||
var serializer = _serviceProvider.GetRequiredService<IMqttPacketSerializer>(); | |||
var logger = _serviceProvider.GetRequiredService<ILogger<MqttChannelCommunicationAdapter>>(); | |||
return new MqttChannelCommunicationAdapter(channel, serializer, logger); | |||
return CreateMqttClient(new MqttNetLogger()); | |||
} | |||
public IMqttCommunicationChannel CreateMqttCommunicationChannel(IMqttClientChannelOptions options) | |||
public IMqttClient CreateMqttClient(IMqttNetLogger logger) | |||
{ | |||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||
switch (options) | |||
{ | |||
case MqttClientTcpOptions tcpOptions: | |||
return CreateTcpChannel(tcpOptions); | |||
case MqttClientWebSocketOptions webSocketOptions: | |||
return CreateWebSocketChannel(webSocketOptions); | |||
default: | |||
throw new NotSupportedException(); | |||
} | |||
} | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
public MqttTcpChannel CreateTcpChannel(MqttClientTcpOptions tcpOptions) | |||
{ | |||
return new MqttTcpChannel(tcpOptions); | |||
return new MqttClient(new MqttClientAdapterFactory(), logger); | |||
} | |||
public MqttWebSocketChannel CreateWebSocketChannel(MqttClientWebSocketOptions webSocketOptions) | |||
public IManagedMqttClient CreateManagedMqttClient() | |||
{ | |||
return new MqttWebSocketChannel(webSocketOptions); | |||
return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger()); | |||
} | |||
public MqttPacketSerializer CreateSerializer(MqttProtocolVersion protocolVersion) | |||
public IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger) | |||
{ | |||
return new MqttPacketSerializer | |||
{ | |||
ProtocolVersion = protocolVersion | |||
}; | |||
} | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
public MqttClientSession CreateClientSession(string clientId, MqttClientSessionsManager clientSessionsManager) | |||
{ | |||
return new MqttClientSession( | |||
clientId, | |||
_serviceProvider.GetRequiredService<IOptions<MqttServerOptions>>(), | |||
clientSessionsManager, | |||
_serviceProvider.GetRequiredService<MqttClientSubscriptionsManager>(), | |||
_serviceProvider.GetRequiredService<ILogger<MqttClientSession>>(), | |||
_serviceProvider.GetRequiredService<ILogger<MqttClientPendingMessagesQueue>>()); | |||
return new ManagedMqttClient(CreateMqttClient(), logger); | |||
} | |||
public IMqttClient CreateMqttClient() | |||
public IMqttServer CreateMqttServer() | |||
{ | |||
return _serviceProvider.GetRequiredService<IMqttClient>(); | |||
var logger = new MqttNetLogger(); | |||
return CreateMqttServer(logger); | |||
} | |||
public IManagedMqttClient CreateManagedMqttClient() | |||
public IMqttServer CreateMqttServer(IMqttNetLogger logger) | |||
{ | |||
return _serviceProvider.GetRequiredService<IManagedMqttClient>(); | |||
} | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
public IMqttServer CreateMqttServer() | |||
{ | |||
return _serviceProvider.GetRequiredService<IMqttServer>(); | |||
return CreateMqttServer(new List<IMqttServerAdapter> { new MqttTcpServerAdapter(logger) }, logger); | |||
} | |||
public IMqttServer CreateMqttServer(Action<MqttServerOptions> configure) | |||
public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) | |||
{ | |||
if (configure == null) throw new ArgumentNullException(nameof(configure)); | |||
var options = _serviceProvider.GetRequiredService<IOptions<MqttServerOptions>>(); | |||
configure(options.Value); | |||
if (adapters == null) throw new ArgumentNullException(nameof(adapters)); | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
return _serviceProvider.GetRequiredService<IMqttServer>(); | |||
return new MqttServer(adapters, logger); | |||
} | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public interface IMqttPacketWithIdentifier | |||
{ | |||
ushort? PacketIdentifier { get; set; } | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public abstract class MqttBasePacket | |||
{ |
@@ -1,7 +1,7 @@ | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier | |||
{ | |||
public ushort PacketIdentifier { get; set; } | |||
public ushort? PacketIdentifier { get; set; } | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttConnAckPacket : MqttBasePacket | |||
{ | |||
@@ -10,7 +10,7 @@ namespace MQTTnet.Core.Packets | |||
public override string ToString() | |||
{ | |||
return nameof(MqttConnAckPacket) + ": [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; | |||
return "ConnAck: [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]"; | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using MQTTnet.Core.Serializer; | |||
using MQTTnet.Serializer; | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttConnectPacket : MqttBasePacket | |||
{ | |||
@@ -20,7 +20,7 @@ namespace MQTTnet.Core.Packets | |||
public override string ToString() | |||
{ | |||
return nameof(MqttConnectPacket) + ": [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; | |||
return "Connect: [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttDisconnectPacket : MqttBasePacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return "Disconnect"; | |||
} | |||
} | |||
} |
@@ -1,6 +1,6 @@ | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public class MqttPacketHeader | |||
{ |
@@ -1,10 +1,10 @@ | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPingReqPacket : MqttBasePacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return nameof(MqttPingReqPacket); | |||
return "PingReq"; | |||
} | |||
} | |||
} |
@@ -1,10 +1,10 @@ | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPingRespPacket : MqttBasePacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return nameof(MqttPingRespPacket); | |||
return "PingResp"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPubAckPacket : MqttBasePublishPacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return "PubAck"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPubCompPacket : MqttBasePublishPacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return "PubComp"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPubRecPacket : MqttBasePublishPacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return "PubRec"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPubRelPacket : MqttBasePublishPacket | |||
{ | |||
public override string ToString() | |||
{ | |||
return "PubRel"; | |||
} | |||
} | |||
} |
@@ -1,7 +1,6 @@ | |||
using System; | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttPublishPacket : MqttBasePublishPacket | |||
{ | |||
@@ -17,9 +16,8 @@ namespace MQTTnet.Core.Packets | |||
public override string ToString() | |||
{ | |||
return nameof(MqttPublishPacket) + | |||
": [Topic=" + Topic + "]" + | |||
" [Payload=" + Convert.ToBase64String(Payload) + "]" + | |||
return "Publish: [Topic=" + Topic + "]" + | |||
" [Payload.Length=" + Payload?.Length + "]" + | |||
" [QoSLevel=" + QualityOfServiceLevel + "]" + | |||
" [Dup=" + Dup + "]" + | |||
" [Retain=" + Retain + "]" + |
@@ -1,19 +1,19 @@ | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using MQTTnet.Core.Protocol; | |||
using MQTTnet.Protocol; | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier | |||
{ | |||
public ushort PacketIdentifier { get; set; } | |||
public ushort? PacketIdentifier { get; set; } | |||
public IList<MqttSubscribeReturnCode> SubscribeReturnCodes { get; } = new List<MqttSubscribeReturnCode>(); | |||
public override string ToString() | |||
{ | |||
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString())); | |||
return nameof(MqttSubAckPacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]"; | |||
return "SubAck: [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]"; | |||
} | |||
} | |||
} |
@@ -1,18 +1,18 @@ | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace MQTTnet.Core.Packets | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier | |||
{ | |||
public ushort PacketIdentifier { get; set; } | |||
public ushort? PacketIdentifier { get; set; } | |||
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>(); | |||
public override string ToString() | |||
{ | |||
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); | |||
return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | |||
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier | |||
{ | |||
public ushort? PacketIdentifier { get; set; } | |||
public override string ToString() | |||
{ | |||
return "UnsubAck: [PacketIdentifier=" + PacketIdentifier + "]"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
using System.Collections.Generic; | |||
namespace MQTTnet.Packets | |||
{ | |||
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier | |||
{ | |||
public ushort? PacketIdentifier { get; set; } | |||
public IList<string> TopicFilters { get; set; } = new List<string>(); | |||
public override string ToString() | |||
{ | |||
var topicFiltersText = string.Join(",", TopicFilters); | |||
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; | |||
} | |||
} | |||
} |