diff --git a/.gitignore b/.gitignore
index d44c4553..569e43ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Build/MQTTnet.AspNetCore.nuspec b/Build/MQTTnet.AspNetCore.nuspec
index eb5f637f..4245077b 100644
--- a/Build/MQTTnet.AspNetCore.nuspec
+++ b/Build/MQTTnet.AspNetCore.nuspec
@@ -2,7 +2,7 @@
MQTTnet.AspNetCore
- 2.5.0
+ 2.7.5
Christian Kratky
Christian Kratky
https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
@@ -10,14 +10,13 @@
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
false
This is a support library to integrate MQTTnet into AspNetCore.
- initial version
+ * Updated to MQTTnet 2.7.5.
- Copyright Christian Kratky 2016-2017
- MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation
+ Copyright Christian Kratky 2016-2018
+ 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
-
-
+
@@ -25,6 +24,5 @@
-
\ No newline at end of file
diff --git a/Build/MQTTnet.Extensions.Rpc.nuspec b/Build/MQTTnet.Extensions.Rpc.nuspec
new file mode 100644
index 00000000..cb31c21b
--- /dev/null
+++ b/Build/MQTTnet.Extensions.Rpc.nuspec
@@ -0,0 +1,47 @@
+
+
+
+ MQTTnet.Extensions.Rpc
+ 2.7.5
+ Christian Kratky
+ Christian Kratky
+ https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
+ https://github.com/chkr1011/MQTTnet
+ https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
+ false
+ This is a extension library which allows executing synchronous device calls including a response using MQTTnet.
+ * Updated to MQTTnet 2.7.5.
+
+ Copyright Christian Kratky 2016-2017
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec
index fe5d708b..4146cd38 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -2,7 +2,7 @@
MQTTnet
- 2.5.0
+ 2.7.5
Christian Kratky
Christian Kratky
https://github.com/chkr1011/MQTTnet/blob/master/LICENSE
@@ -10,49 +10,59 @@
https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png
false
MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).
- * [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)
+ * [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).
- Copyright Christian Kratky 2016-2017
- MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation
+ Copyright Christian Kratky 2016-2018
+ 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
-
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
+
+
+
-
-
-
+
+
\ No newline at end of file
diff --git a/Build/build.ps1 b/Build/build.ps1
index 6284473a..021ca1d6 100644
--- a/Build/build.ps1
+++ b/Build/build.ps1
@@ -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"
+
+ # 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
- 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
-}
-
+ .\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
+}
\ No newline at end of file
diff --git a/Build/codeSigningKey.pfx b/Build/codeSigningKey.pfx
new file mode 100644
index 00000000..9374a5db
Binary files /dev/null and b/Build/codeSigningKey.pfx differ
diff --git a/Documents/Import_CodeSigningKey.md b/Documents/Import_CodeSigningKey.md
new file mode 100644
index 00000000..7e966e4a
--- /dev/null
+++ b/Documents/Import_CodeSigningKey.md
@@ -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
\ No newline at end of file
diff --git a/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
new file mode 100644
index 00000000..3f4128e0
--- /dev/null
+++ b/Extensions/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netstandard1.3;netstandard2.0;net452;net461;uap10.0
+ 0.0.0.0
+ 0.0.0.0
+
+
+
+
+ 0.0.0.0
+
+
+
+ false
+ UAP,Version=v10.0
+ UAP
+ 10.0.16299.0
+ 10.0.10240.0
+ .NETCore
+ v5.0
+ $(DefineConstants);WINDOWS_UWP
+ en
+ $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets
+
+
+
+
+
+
+
diff --git a/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
new file mode 100644
index 00000000..938fcb4c
--- /dev/null
+++ b/Extensions/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
@@ -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> _waitingCalls = new ConcurrentDictionary>();
+ 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 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();
+ 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 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();
+ }
+ }
+}
diff --git a/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c b/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c
new file mode 100644
index 00000000..5f282702
--- /dev/null
+++ b/Extensions/MQTTnet.Extensions.Rpc/SampleCCode.c
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
index 06ca74d4..5a25712d 100644
--- a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
+++ b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
@@ -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();
- using (var webSocket = await context.WebSockets.AcceptWebSocketAsync("mqtt"))
+ using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subprotocol))
{
await adapter.AcceptWebSocketAsync(webSocket);
}
diff --git a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj
index 5142cc15..a19f2b3b 100644
--- a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj
+++ b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj
@@ -2,12 +2,23 @@
netstandard2.0
+ 0.0.0.0
+ 0.0.0.0
+ 0.0.0.0
+
+
+
+
+
+
+
+ RELEASE;NETSTANDARD2_0
-
-
-
+
+
+
diff --git a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs
index db725735..8e6761ad 100644
--- a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs
+++ b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs
@@ -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 options, IEnumerable adapters, ILogger logger, MqttClientSessionsManager clientSessionsManager)
- : base(options, adapters, logger, clientSessionsManager)
+ private readonly IMqttServerOptions _options;
+
+ public MqttHostedServer(IMqttServerOptions options, IEnumerable 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)
diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
index 5cd5d555..c84b9be3 100644
--- a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
+++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
@@ -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 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();
- }
- }
}
}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs
new file mode 100644
index 00000000..7325a5f8
--- /dev/null
+++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerChannel.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
index f915ab43..61354cd9 100644
--- a/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
+++ b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
@@ -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(new MqttNetLogger());
+ services.AddSingleton();
services.AddSingleton(s => s.GetService());
services.AddSingleton(s => s.GetService());
- services.AddSingleton();
services.AddSingleton();
- services.AddSingleton(s => s.GetService());
+ services.AddSingleton();
+ services.AddSingleton(s => s.GetService());
+ services.AddSingleton(s => s.GetService());
return services;
}
diff --git a/MQTTnet.Core/Adapter/IMqttCommunicationAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs
similarity index 78%
rename from MQTTnet.Core/Adapter/IMqttCommunicationAdapter.cs
rename to Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs
index 78586f66..7a5c02a2 100644
--- a/MQTTnet.Core/Adapter/IMqttCommunicationAdapter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttChannelAdapter.cs
@@ -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; }
diff --git a/MQTTnet.Core/Adapter/IMqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs
similarity index 66%
rename from MQTTnet.Core/Adapter/IMqttServerAdapter.cs
rename to Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs
index 9209185e..bd583f54 100644
--- a/MQTTnet.Core/Adapter/IMqttServerAdapter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Adapter/IMqttServerAdapter.cs
@@ -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 ClientAccepted;
- Task StartAsync(MqttServerOptions options);
+ Task StartAsync(IMqttServerOptions options);
Task StopAsync();
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs
new file mode 100644
index 00000000..c2f9ba43
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttChannelAdapter.cs
@@ -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("Connecting [Timeout={0}]", timeout);
+
+ return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout));
+ }
+
+ public Task DisconnectAsync(TimeSpan timeout)
+ {
+ ThrowIfDisposed();
+ _logger.Verbose("Disconnecting [Timeout={0}]", timeout);
+
+ return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout));
+ }
+
+ public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable 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("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 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("RX <<< {0}", packet);
+ }
+ finally
+ {
+ receivedMqttPacket?.Dispose();
+ }
+ }).ConfigureAwait(false);
+
+ return packet;
+ }
+
+ private static async Task 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 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));
+ }
+ }
+ }
+}
diff --git a/MQTTnet.Core/Adapter/MqttConnectingFailedException.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs
similarity index 79%
rename from MQTTnet.Core/Adapter/MqttConnectingFailedException.cs
rename to Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs
index 045d0d13..68445cfd 100644
--- a/MQTTnet.Core/Adapter/MqttConnectingFailedException.cs
+++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttConnectingFailedException.cs
@@ -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
{
diff --git a/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
similarity index 61%
rename from MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
rename to Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
index 6caca0e9..42852493 100644
--- a/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
+++ b/Frameworks/MQTTnet.NetStandard/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
@@ -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; }
}
diff --git a/MQTTnet.Core/Adapter/ReceivedMqttPacket.cs b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs
similarity index 67%
rename from MQTTnet.Core/Adapter/ReceivedMqttPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs
index f8adc2eb..a44fb54e 100644
--- a/MQTTnet.Core/Adapter/ReceivedMqttPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Adapter/ReceivedMqttPacket.cs
@@ -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();
+ }
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs b/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs
new file mode 100644
index 00000000..b1bf62f5
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/ApplicationMessagePublisherExtensions.cs
@@ -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());
+ }
+ }
+}
diff --git a/MQTTnet.Core/Channel/IMqttCommunicationChannel.cs b/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs
similarity index 52%
rename from MQTTnet.Core/Channel/IMqttCommunicationChannel.cs
rename to Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs
index 8c746d1e..b332884d 100644
--- a/MQTTnet.Core/Channel/IMqttCommunicationChannel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Channel/IMqttChannel.cs
@@ -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();
diff --git a/MQTTnet.Core/Client/IMqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs
similarity index 78%
rename from MQTTnet.Core/Client/IMqttClient.cs
rename to Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs
index 6a39e211..40d1a48a 100644
--- a/MQTTnet.Core/Client/IMqttClient.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClient.cs
@@ -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> SubscribeAsync(IEnumerable topicFilters);
- Task UnsubscribeAsync(IEnumerable topicFilters);
+ Task UnsubscribeAsync(IEnumerable topics);
}
}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs
new file mode 100644
index 00000000..c2a7e74b
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientAdapterFactory.cs
@@ -0,0 +1,10 @@
+using MQTTnet.Adapter;
+using MQTTnet.Diagnostics;
+
+namespace MQTTnet.Client
+{
+ public interface IMqttClientAdapterFactory
+ {
+ IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetLogger logger);
+ }
+}
diff --git a/MQTTnet.Core/Client/IMqttClientChannelOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs
similarity index 77%
rename from MQTTnet.Core/Client/IMqttClientChannelOptions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs
index 004430f9..22249632 100644
--- a/MQTTnet.Core/Client/IMqttClientChannelOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientChannelOptions.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public interface IMqttClientChannelOptions
{
diff --git a/MQTTnet.Core/Client/IMqttClientCredentials.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs
similarity index 78%
rename from MQTTnet.Core/Client/IMqttClientCredentials.cs
rename to Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs
index c4dfc127..d214d64f 100644
--- a/MQTTnet.Core/Client/IMqttClientCredentials.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientCredentials.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public interface IMqttClientCredentials
{
diff --git a/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs
new file mode 100644
index 00000000..444c16fc
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientFactory.cs
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Client/IMqttClientOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs
similarity index 63%
rename from MQTTnet.Core/Client/IMqttClientOptions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs
index ae771922..647f688b 100644
--- a/MQTTnet.Core/Client/IMqttClientOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/IMqttClientOptions.cs
@@ -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; }
- ///
- /// The LogId is used to create a scope to correlate logging. If no value is provided the ClientId is used instead
- ///
- 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; }
diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs
new file mode 100644
index 00000000..90790d5a
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClient.cs
@@ -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 Connected;
+ public event EventHandler Disconnected;
+ public event EventHandler ApplicationMessageReceived;
+
+ public bool IsConnected { get; private set; }
+
+ public async Task 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("Trying to connect with server.");
+ await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false);
+ _logger.Verbose("Connection with server established.");
+
+ await StartReceivingPacketsAsync().ConfigureAwait(false);
+
+ var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false);
+ _logger.Verbose("MQTT connection with server established.");
+
+ _sendTracker.Restart();
+
+ if (_options.KeepAlivePeriod != TimeSpan.Zero)
+ {
+ StartSendingKeepAliveMessages();
+ }
+
+ IsConnected = true;
+ Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
+
+ _logger.Info("Connected.");
+ return new MqttClientConnectResult(connectResponse.IsSessionPresent);
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(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> SubscribeAsync(IEnumerable topicFilters)
+ {
+ if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+
+ ThrowIfNotConnected();
+
+ var subscribePacket = new MqttSubscribePacket
+ {
+ PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
+ TopicFilters = topicFilters.ToList()
+ };
+
+ var response = await SendAndReceiveAsync(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 topicFilters)
+ {
+ if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+
+ ThrowIfNotConnected();
+
+ var unsubscribePacket = new MqttUnsubscribePacket
+ {
+ PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(),
+ TopicFilters = topicFilters.ToList()
+ };
+
+ await SendAndReceiveAsync(unsubscribePacket).ConfigureAwait(false);
+ }
+
+ public async Task PublishAsync(IEnumerable 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().ToArray()).ConfigureAwait(false);
+ break;
+ }
+ case MqttQualityOfServiceLevel.AtLeastOnce:
+ {
+ foreach (var publishPacket in qosGroup)
+ {
+ publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
+ await SendAndReceiveAsync(publishPacket).ConfigureAwait(false);
+ }
+
+ break;
+ }
+ case MqttQualityOfServiceLevel.ExactlyOnce:
+ {
+ foreach (var publishPacket in qosGroup)
+ {
+ publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
+
+ var pubRecPacket = await SendAndReceiveAsync(publishPacket).ConfigureAwait(false);
+ var pubRelPacket = new MqttPubRelPacket
+ {
+ PacketIdentifier = pubRecPacket.PacketIdentifier
+ };
+
+ await SendAndReceiveAsync(pubRelPacket).ConfigureAwait(false);
+ }
+
+ break;
+ }
+ default:
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+
+ _adapter?.Dispose();
+ }
+
+ private async Task 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(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(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("Disconnected from adapter.");
+ }
+ catch (Exception adapterException)
+ {
+ _logger.Warning(adapterException, "Error while disconnecting from adapter.");
+ }
+ finally
+ {
+ _adapter?.Dispose();
+ _adapter = null;
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+
+ _logger.Info("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(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(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 SendAndReceiveAsync(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("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(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(exception, "MQTT communication exception while sending/receiving keep alive packets.");
+ }
+ else
+ {
+ _logger.Error(exception, "Unhandled exception while sending/receiving keep alive packets.");
+ }
+
+ await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
+ }
+ finally
+ {
+ _logger.Verbose("Stopped sending keep alive packets.");
+ }
+ }
+
+ private async Task ReceivePacketsAsync()
+ {
+ _logger.Verbose("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(exception, "MQTT communication exception while receiving packets.");
+ }
+ else
+ {
+ _logger.Error(exception, "Unhandled exception while receiving packets.");
+ }
+
+ await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
+ }
+ finally
+ {
+ _logger.Verbose("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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Client/MqttClientConnectResult.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs
similarity index 87%
rename from MQTTnet.Core/Client/MqttClientConnectResult.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs
index 57db57df..6044bf50 100644
--- a/MQTTnet.Core/Client/MqttClientConnectResult.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectResult.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public class MqttClientConnectResult
{
diff --git a/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs
similarity index 90%
rename from MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs
index 107a2c95..c8c6ce56 100644
--- a/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientConnectedEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public class MqttClientConnectedEventArgs : EventArgs
{
diff --git a/MQTTnet.Core/Client/MqttClientCredentials.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs
similarity index 83%
rename from MQTTnet.Core/Client/MqttClientCredentials.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs
index 085778a3..feaa1e6c 100644
--- a/MQTTnet.Core/Client/MqttClientCredentials.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientCredentials.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public class MqttClientCredentials : IMqttClientCredentials
{
diff --git a/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs
similarity index 67%
rename from MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs
index 1e9609ef..2161cc26 100644
--- a/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientDisconnectedEventArgs.cs
@@ -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; }
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs
new file mode 100644
index 00000000..a7548dee
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientExtensions.cs
@@ -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> 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> 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> 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Client/MqttClientOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs
similarity index 82%
rename from MQTTnet.Core/Client/MqttClientOptions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs
index 1b1c1bb8..9640a474 100644
--- a/MQTTnet.Core/Client/MqttClientOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptions.cs
@@ -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");
- ///
- 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);
diff --git a/MQTTnet.Core/Client/MqttClientOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs
similarity index 66%
rename from MQTTnet.Core/Client/MqttClientOptionsBuilder.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs
index 1eb2a347..ad766c3f 100644
--- a/MQTTnet.Core/Client/MqttClientOptionsBuilder.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientOptionsBuilder.cs
@@ -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;
}
}
diff --git a/MQTTnet.Core/Client/MqttClientTcpOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs
similarity index 74%
rename from MQTTnet.Core/Client/MqttClientTcpOptions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs
index e5cf8d6c..ac96ce63 100644
--- a/MQTTnet.Core/Client/MqttClientTcpOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptions.cs
@@ -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();
}
}
diff --git a/MQTTnet.Core/Client/MqttClientTcpOptionsExtensions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs
similarity index 93%
rename from MQTTnet.Core/Client/MqttClientTcpOptionsExtensions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs
index cada6f8b..460069a7 100644
--- a/MQTTnet.Core/Client/MqttClientTcpOptionsExtensions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTcpOptionsExtensions.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public static class MqttClientTcpOptionsExtensions
{
diff --git a/MQTTnet.Core/Client/MqttClientTlsOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs
similarity index 92%
rename from MQTTnet.Core/Client/MqttClientTlsOptions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs
index 33cd691f..6478b267 100644
--- a/MQTTnet.Core/Client/MqttClientTlsOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientTlsOptions.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace MQTTnet.Core.Client
+namespace MQTTnet.Client
{
public class MqttClientTlsOptions
{
diff --git a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs
similarity index 77%
rename from MQTTnet.Core/Client/MqttClientWebSocketOptions.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs
index 25e4ae81..114283d4 100644
--- a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttClientWebSocketOptions.cs
@@ -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 RequestHeaders { get; set; }
- public ICollection SubProtocols { get; set; }
+ public ICollection SubProtocols { get; set; } = new List { "mqtt" };
public CookieContainer CookieContainer { get; set; }
diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs
new file mode 100644
index 00000000..8b09fbce
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketDispatcher.cs
@@ -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>> _awaiters = new ConcurrentDictionary>>();
+ private readonly IMqttNetLogger _logger;
+
+ public MqttPacketDispatcher(IMqttNetLogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task 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("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 AddPacketAwaiter(Type responseType, ushort? identifier)
+ {
+ var tcs = new TaskCompletionSource();
+
+ if (!identifier.HasValue)
+ {
+ identifier = 0;
+ }
+
+ var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary>());
+ 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>());
+ byId.TryRemove(identifier.Value, out var _);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs
new file mode 100644
index 00000000..dc099183
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttPacketIdentifierProvider.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/MQTTnet.Core/Client/MqttSubscribeResult.cs b/Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs
similarity index 79%
rename from MQTTnet.Core/Client/MqttSubscribeResult.cs
rename to Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs
index 9dd49449..2b7c46da 100644
--- a/MQTTnet.Core/Client/MqttSubscribeResult.cs
+++ b/Frameworks/MQTTnet.NetStandard/Client/MqttSubscribeResult.cs
@@ -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
{
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs
new file mode 100644
index 00000000..6ed16de2
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/IMqttNetLogger.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace MQTTnet.Diagnostics
+{
+ public interface IMqttNetLogger
+ {
+ event EventHandler LogMessagePublished;
+
+ void Verbose(string message, params object[] parameters);
+
+ void Info(string message, params object[] parameters);
+
+ void Warning(Exception exception, string message, params object[] parameters);
+
+ void Warning(string message, params object[] parameters);
+
+ void Error(Exception exception, string message, params object[] parameters);
+
+ void Error(string message, params object[] parameters);
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs
new file mode 100644
index 00000000..a42857ea
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetGlobalLogger.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace MQTTnet.Diagnostics
+{
+ public static class MqttNetGlobalLogger
+ {
+ public static event EventHandler 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));
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs
new file mode 100644
index 00000000..cdc7a78a
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogLevel.cs
@@ -0,0 +1,13 @@
+namespace MQTTnet.Diagnostics
+{
+ public enum MqttNetLogLevel
+ {
+ Verbose,
+
+ Info,
+
+ Warning,
+
+ Error
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs
new file mode 100644
index 00000000..6035a89c
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessage.cs
@@ -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;
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs
new file mode 100644
index 00000000..2407752b
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogMessagePublishedEventArgs.cs
@@ -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; }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs
new file mode 100644
index 00000000..53598b2b
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/MqttNetLogger.cs
@@ -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 LogMessagePublished;
+
+ public void Verbose(string message, params object[] parameters)
+ {
+ Publish(MqttNetLogLevel.Verbose, null, message, parameters);
+ }
+
+ public void Info(string message, params object[] parameters)
+ {
+ Publish(MqttNetLogLevel.Info, null, message, parameters);
+ }
+
+ public void Warning(Exception exception, string message, params object[] parameters)
+ {
+ Publish(MqttNetLogLevel.Warning, exception, message, parameters);
+ }
+
+ public void Warning(string message, params object[] parameters)
+ {
+ Warning(null, message, parameters);
+ }
+
+ public void Error(Exception exception, string message, params object[] parameters)
+ {
+ Publish(MqttNetLogLevel.Error, exception, message, parameters);
+ }
+
+ public void Error(string message, params object[] parameters)
+ {
+ Warning(null, message, parameters);
+ }
+
+ private void Publish(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));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs b/Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs
new file mode 100644
index 00000000..52582769
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Diagnostics/TargetFrameworkInfoProvider.cs
@@ -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
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Exceptions/MqttCommunicationException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs
similarity index 92%
rename from MQTTnet.Core/Exceptions/MqttCommunicationException.cs
rename to Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs
index e019f4ee..4631de83 100644
--- a/MQTTnet.Core/Exceptions/MqttCommunicationException.cs
+++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationException.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Exceptions
+namespace MQTTnet.Exceptions
{
public class MqttCommunicationException : Exception
{
diff --git a/MQTTnet.Core/Exceptions/MqttCommunicationTimedOutException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs
similarity index 73%
rename from MQTTnet.Core/Exceptions/MqttCommunicationTimedOutException.cs
rename to Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs
index e7240072..7d0adcd4 100644
--- a/MQTTnet.Core/Exceptions/MqttCommunicationTimedOutException.cs
+++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttCommunicationTimedOutException.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Exceptions
+namespace MQTTnet.Exceptions
{
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException
{
diff --git a/MQTTnet.Core/Exceptions/MqttProtocolViolationException.cs b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs
similarity index 86%
rename from MQTTnet.Core/Exceptions/MqttProtocolViolationException.cs
rename to Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs
index cef43f5c..8756b3f8 100644
--- a/MQTTnet.Core/Exceptions/MqttProtocolViolationException.cs
+++ b/Frameworks/MQTTnet.NetStandard/Exceptions/MqttProtocolViolationException.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Exceptions
+namespace MQTTnet.Exceptions
{
public sealed class MqttProtocolViolationException : Exception
{
diff --git a/MQTTnet.Core/IApplicationMessagePublisher.cs b/Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs
similarity index 90%
rename from MQTTnet.Core/IApplicationMessagePublisher.cs
rename to Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs
index 2f6715b8..e137669e 100644
--- a/MQTTnet.Core/IApplicationMessagePublisher.cs
+++ b/Frameworks/MQTTnet.NetStandard/IApplicationMessagePublisher.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace MQTTnet.Core
+namespace MQTTnet
{
public interface IApplicationMessagePublisher
{
diff --git a/MQTTnet.Core/IApplicationMessageReceiver.cs b/Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs
similarity index 78%
rename from MQTTnet.Core/IApplicationMessageReceiver.cs
rename to Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs
index 379ba69c..b315067c 100644
--- a/MQTTnet.Core/IApplicationMessageReceiver.cs
+++ b/Frameworks/MQTTnet.NetStandard/IApplicationMessageReceiver.cs
@@ -1,7 +1,6 @@
using System;
-using MQTTnet.Core.Client;
-namespace MQTTnet.Core
+namespace MQTTnet
{
public interface IApplicationMessageReceiver
{
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs
new file mode 100644
index 00000000..4692d7ee
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttClientAdapterFactory.cs
@@ -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();
+ }
+ }
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs
similarity index 71%
rename from Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttTcpChannel.cs
rename to Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs
index 6e311d67..a9cd18e7 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttTcpChannel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.Uwp.cs
@@ -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> CustomIgnorableServerCertificateErrorsResolver { get; set; }
@@ -71,24 +76,56 @@ namespace MQTTnet.Implementations
public void Dispose()
{
- RawReceiveStream?.Dispose();
- RawReceiveStream = null;
+ try
+ {
+ SendStream?.Dispose();
+ }
+ catch (ObjectDisposedException)
+ {
+ }
+ catch (NullReferenceException)
+ {
+ }
+ finally
+ {
+ SendStream = null;
+ }
- SendStream?.Dispose();
- SendStream = null;
+ try
+ {
+ ReceiveStream?.Dispose();
+ }
+ catch (ObjectDisposedException)
+ {
+ }
+ catch (NullReferenceException)
+ {
+ }
+ finally
+ {
+ ReceiveStream = null;
+ }
- ReceiveStream?.Dispose();
- 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)
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
similarity index 58%
rename from Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttTcpChannel.cs
rename to Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
index 62bb8787..8d43d878 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttTcpChannel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
@@ -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;
}
///
@@ -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 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);
- _sslStream?.Dispose();
- _sslStream = null;
+ 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;
+ }
+
+ 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
\ No newline at end of file
+#endif
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs
similarity index 63%
rename from Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttServerAdapter.cs
rename to Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs
index 7d9d25f1..e71d6fff 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/Uap/MqttServerAdapter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.Uwp.cs
@@ -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 _logger;
- private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;
+ private readonly IMqttNetLogger _logger;
private StreamSocketListener _defaultEndpointSocket;
- public MqttServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
+ public MqttTcpServerAdapter(IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
}
public event EventHandler 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(exception, "Error while accepting connection at default endpoint.");
}
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs
similarity index 64%
rename from Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttServerAdapter.cs
rename to Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs
index 1a6e5b40..dc33a3f2 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/NetStandard/MqttServerAdapter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpServerAdapter.cs
@@ -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 _logger;
- private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;
+ private readonly IMqttNetLogger _logger;
private CancellationTokenSource _cancellationTokenSource;
private Socket _defaultEndpointSocket;
private Socket _tlsEndpointSocket;
private X509Certificate2 _tlsCertificate;
- public MqttServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
+ public MqttTcpServerAdapter(IMqttNetLogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
}
public event EventHandler 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(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(exception, "Error while accepting connection at TLS endpoint.");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
index c120775b..057a07e5 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
@@ -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;
+ }
}
}
}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
index c27e4801..bad03935 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
@@ -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 _buffer = new Queue(MqttWebSocketChannel.BufferSize);
private readonly WebSocket _webSocket;
-
+
public WebSocketStream(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
@@ -34,33 +38,9 @@ namespace MQTTnet.Implementations
{
}
- public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ public override Task FlushAsync(CancellationToken cancellationToken)
{
- var currentOffset = offset;
- var targetOffset = offset + count;
- while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset)
- {
- var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
- currentOffset += response.Count;
- count -= response.Count;
-
- if (response.MessageType == WebSocketMessageType.Close)
- {
- await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
- }
- }
-
- if (_webSocket.State == WebSocketState.Closed)
- {
- throw new MqttCommunicationException( "connection closed" );
- }
-
- return currentOffset - offset;
- }
-
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
+ return Task.FromResult(0);
}
public override int Read(byte[] buffer, int offset, int count)
@@ -68,11 +48,61 @@ namespace MQTTnet.Implementations
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
+ public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ 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)
+ {
+ await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
+
+ while (count > 0 && _buffer.Any())
+ {
+ buffer[offset] = _buffer.Dequeue();
+ count--;
+ bytesRead++;
+ offset++;
+ }
+
+ if (count == 0)
+ {
+ return bytesRead;
+ }
+ }
+
+ if (_webSocket.State == WebSocketState.Closed)
+ {
+ throw new MqttCommunicationException("WebSocket connection closed.");
+ }
+
+ return bytesRead;
+ }
+
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return _webSocket.SendAsync(new ArraySegment(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
+ }
+
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
@@ -82,5 +112,26 @@ namespace MQTTnet.Implementations
{
throw new NotSupportedException();
}
+
+ private async Task FetchChunkAsync(CancellationToken cancellationToken)
+ {
+ var response = await _webSocket.ReceiveAsync(new ArraySegment(_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.");
+ }
+ }
}
}
diff --git a/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs b/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs
similarity index 94%
rename from MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs
rename to Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs
index 7ca7d005..b1d1a389 100644
--- a/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Internal/MqttApplicationMessageExtensions.cs
@@ -1,6 +1,6 @@
-using MQTTnet.Core.Packets;
+using MQTTnet.Packets;
-namespace MQTTnet.Core.Internal
+namespace MQTTnet.Internal
{
internal static class MqttApplicationMessageExtensions
{
diff --git a/MQTTnet.Core/Internal/TaskExtensions.cs b/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs
similarity index 74%
rename from MQTTnet.Core/Internal/TaskExtensions.cs
rename to Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs
index 45b3510b..876c5495 100644
--- a/MQTTnet.Core/Internal/TaskExtensions.cs
+++ b/Frameworks/MQTTnet.NetStandard/Internal/TaskExtensions.cs
@@ -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 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)
@@ -66,7 +70,7 @@ namespace MQTTnet.Core.Internal
}
finally
{
- cancellationTokenSource.Cancel();
+ timeoutCts.Cancel();
}
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
index 37b7e137..cb154c24 100644
--- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
+++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
@@ -1,54 +1,65 @@
- netstandard1.3;net451;uap10.0
+ netstandard1.3;netstandard2.0;net452;net461;uap10.0
+ netstandard1.3;netstandard2.0
MQTTnet
MQTTnet
- 2.5.0.0
- 2.5.0.0
+ False
+ Full
+ 0.0.0.0
+ 0.0.0.0
0.0.0.0
+ false
+ false
false
UAP,Version=v10.0
UAP
- 10.0.15063.0
+ 10.0.16299.0
10.0.10240.0
.NETCore
v5.0
$(DefineConstants);WINDOWS_UWP
+ en
$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets
-
-
-
-
-
+
+ RELEASE;NETSTANDARD1_3
+
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs
new file mode 100644
index 00000000..fa82c0a4
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ApplicationMessageProcessedEventArgs.cs
@@ -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;
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs
similarity index 62%
rename from MQTTnet.Core/ManagedClient/IManagedMqttClient.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs
index 8d2ac8cc..97cdeca9 100644
--- a/MQTTnet.Core/ManagedClient/IManagedMqttClient.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClient.cs
@@ -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 Connected;
event EventHandler Disconnected;
+ event EventHandler ApplicationMessageProcessed;
+
Task StartAsync(IManagedMqttClientOptions options);
Task StopAsync();
Task SubscribeAsync(IEnumerable topicFilters);
- Task UnsubscribeAsync(IEnumerable topicFilters);
+ Task UnsubscribeAsync(IEnumerable topics);
}
}
\ No newline at end of file
diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs
similarity index 77%
rename from MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs
index 298de9a1..fbb87b79 100644
--- a/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientOptions.cs
@@ -1,7 +1,7 @@
using System;
-using MQTTnet.Core.Client;
+using MQTTnet.Client;
-namespace MQTTnet.Core.ManagedClient
+namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClientOptions
{
diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs
similarity index 88%
rename from MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs
index 39ea89cc..28bf5703 100644
--- a/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/IManagedMqttClientStorage.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace MQTTnet.Core.ManagedClient
+namespace MQTTnet.ManagedClient
{
public interface IManagedMqttClientStorage
{
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs
similarity index 50%
rename from MQTTnet.Core/ManagedClient/ManagedMqttClient.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs
index 4eb40b23..f1ce45d9 100644
--- a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClient.cs
@@ -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 _messageQueue = new BlockingCollection();
- private readonly HashSet _subscriptions = new HashSet();
+ private readonly Dictionary _subscriptions = new Dictionary();
+ private readonly SemaphoreSlim _subscriptionsSemaphore = new SemaphoreSlim(1, 1);
+ private readonly List _unsubscriptions = new List();
private readonly IMqttClient _mqttClient;
- private readonly ILogger _logger;
+ private readonly IMqttNetLogger _logger;
private CancellationTokenSource _connectionCancellationToken;
private CancellationTokenSource _publishingCancellationToken;
+ private ManagedMqttClientStorageManager _storageManager;
private IManagedMqttClientOptions _options;
+
private bool _subscriptionsNotPushed;
-
- public ManagedMqttClient(ILogger 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 Connected;
public event EventHandler Disconnected;
public event EventHandler ApplicationMessageReceived;
+ public event EventHandler 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("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 topicFilters)
+ public async Task SubscribeAsync(IEnumerable 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 topicFilters)
+ public async Task UnsubscribeAsync(IEnumerable 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(exception, "Unhandled exception while maintaining connection.");
+ }
+ finally
+ {
+ await _mqttClient.DisconnectAsync().ConfigureAwait(false);
+ _logger.Info("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(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(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(exception, "Unhandled exception while publishing queued application messages.");
+ }
finally
{
- _logger.LogInformation("Stopped publishing messages");
+ _logger.Verbose("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(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(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(nameof(ManagedMqttClient), "Synchronizing subscriptions");
List subscriptions;
- lock (_subscriptions)
+ List unsubscriptions;
+
+ await _subscriptionsSemaphore.WaitAsync().ConfigureAwait(false);
+ try
{
- subscriptions = _subscriptions.ToList();
+ subscriptions = _subscriptions.Select(i => new TopicFilter(i.Key, i.Value)).ToList();
+
+ unsubscriptions = new List(_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(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;
+ }
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs
new file mode 100644
index 00000000..84b37858
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientExtensions.cs
@@ -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);
+ }
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs
similarity index 83%
rename from MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs
index 2e24fd0a..813aee8a 100644
--- a/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptions.cs
@@ -1,7 +1,7 @@
using System;
-using MQTTnet.Core.Client;
+using MQTTnet.Client;
-namespace MQTTnet.Core.ManagedClient
+namespace MQTTnet.ManagedClient
{
public class ManagedMqttClientOptions : IManagedMqttClientOptions
{
diff --git a/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs
new file mode 100644
index 00000000..4d65bdbb
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientOptionsBuilder.cs
@@ -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 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;
+ }
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs
similarity index 70%
rename from MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs
index 0810f36e..0e71776e 100644
--- a/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ManagedMqttClientStorageManager.cs
@@ -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 _applicationMessages = new List();
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 ?? throw new ArgumentNullException(nameof(storage));
+ }
+
+ public async Task LoadQueuedMessagesAsync()
+ {
+ var loadedMessages = await _storage.LoadQueuedMessagesAsync().ConfigureAwait(false);
+ foreach (var loadedMessage in loadedMessages)
{
- _storage = storage;
- }
- finally
- {
- _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)
{
diff --git a/MQTTnet.Core/ManagedClient/ReconnectionResult.cs b/Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs
similarity index 74%
rename from MQTTnet.Core/ManagedClient/ReconnectionResult.cs
rename to Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs
index fae07706..42566e3e 100644
--- a/MQTTnet.Core/ManagedClient/ReconnectionResult.cs
+++ b/Frameworks/MQTTnet.NetStandard/ManagedClient/ReconnectionResult.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.ManagedClient
+namespace MQTTnet.ManagedClient
{
public enum ReconnectionResult
{
diff --git a/MQTTnet.Core/MqttApplicationMessage.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs
similarity index 94%
rename from MQTTnet.Core/MqttApplicationMessage.cs
rename to Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs
index b07ee18f..387acb21 100644
--- a/MQTTnet.Core/MqttApplicationMessage.cs
+++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessage.cs
@@ -1,7 +1,7 @@
using System;
-using MQTTnet.Core.Protocol;
+using MQTTnet.Protocol;
-namespace MQTTnet.Core
+namespace MQTTnet
{
public sealed class MqttApplicationMessage
{
diff --git a/MQTTnet.Core/MqttApplicationMessageBuilder.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs
similarity index 97%
rename from MQTTnet.Core/MqttApplicationMessageBuilder.cs
rename to Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs
index c2e04318..ce949b85 100644
--- a/MQTTnet.Core/MqttApplicationMessageBuilder.cs
+++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageBuilder.cs
@@ -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
{
diff --git a/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs
new file mode 100644
index 00000000..dac9998a
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageExtensions.cs
@@ -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);
+ }
+ }
+}
diff --git a/MQTTnet.Core/MqttApplicationMessageReceivedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs
similarity index 95%
rename from MQTTnet.Core/MqttApplicationMessageReceivedEventArgs.cs
rename to Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs
index 3de0db54..a16a81d1 100644
--- a/MQTTnet.Core/MqttApplicationMessageReceivedEventArgs.cs
+++ b/Frameworks/MQTTnet.NetStandard/MqttApplicationMessageReceivedEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core
+namespace MQTTnet
{
public sealed class MqttApplicationMessageReceivedEventArgs : EventArgs
{
diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs
index 50cdddea..e3b3549a 100644
--- a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs
+++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs
@@ -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()
- .AddMqttTrace();
-
- return serviceProvider;
- }
-
- public MqttFactory()
- : this(BuildServiceProvider())
- {
- }
-
- public MqttFactory(IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider;
- }
-
- public ILoggerFactory GetLoggerFactory()
- {
- return _serviceProvider.GetRequiredService();
- }
-
- public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options)
- {
- var logger = _serviceProvider.GetRequiredService>();
- return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options.ChannelOptions), CreateSerializer(options.ProtocolVersion), logger);
- }
-
- public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel)
- {
- var serializer = _serviceProvider.GetRequiredService();
- var logger = _serviceProvider.GetRequiredService>();
- return new MqttChannelCommunicationAdapter(channel, serializer, logger);
- }
-
- public IMqttCommunicationChannel CreateMqttCommunicationChannel(IMqttClientChannelOptions options)
- {
- 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();
- }
- }
-
- public MqttTcpChannel CreateTcpChannel(MqttClientTcpOptions tcpOptions)
- {
- return new MqttTcpChannel(tcpOptions);
- }
-
- public MqttWebSocketChannel CreateWebSocketChannel(MqttClientWebSocketOptions webSocketOptions)
- {
- return new MqttWebSocketChannel(webSocketOptions);
- }
-
- public MqttPacketSerializer CreateSerializer(MqttProtocolVersion protocolVersion)
- {
- return new MqttPacketSerializer
- {
- ProtocolVersion = protocolVersion
- };
- }
-
- public MqttClientSession CreateClientSession(string clientId, MqttClientSessionsManager clientSessionsManager)
- {
- return new MqttClientSession(
- clientId,
- _serviceProvider.GetRequiredService>(),
- clientSessionsManager,
- _serviceProvider.GetRequiredService(),
- _serviceProvider.GetRequiredService>(),
- _serviceProvider.GetRequiredService>());
- }
-
public IMqttClient CreateMqttClient()
{
- return _serviceProvider.GetRequiredService();
+ return CreateMqttClient(new MqttNetLogger());
+ }
+
+ public IMqttClient CreateMqttClient(IMqttNetLogger logger)
+ {
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ return new MqttClient(new MqttClientAdapterFactory(), logger);
}
public IManagedMqttClient CreateManagedMqttClient()
{
- return _serviceProvider.GetRequiredService();
+ return new ManagedMqttClient(CreateMqttClient(), new MqttNetLogger());
+ }
+
+ public IManagedMqttClient CreateManagedMqttClient(IMqttNetLogger logger)
+ {
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ return new ManagedMqttClient(CreateMqttClient(), logger);
}
public IMqttServer CreateMqttServer()
{
- return _serviceProvider.GetRequiredService();
+ var logger = new MqttNetLogger();
+ return CreateMqttServer(logger);
}
- public IMqttServer CreateMqttServer(Action configure)
+ public IMqttServer CreateMqttServer(IMqttNetLogger logger)
{
- if (configure == null) throw new ArgumentNullException(nameof(configure));
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
- var options = _serviceProvider.GetRequiredService>();
- configure(options.Value);
+ return CreateMqttServer(new List { new MqttTcpServerAdapter(logger) }, logger);
+ }
- return _serviceProvider.GetRequiredService();
+ public IMqttServer CreateMqttServer(IEnumerable adapters, IMqttNetLogger logger)
+ {
+ if (adapters == null) throw new ArgumentNullException(nameof(adapters));
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ return new MqttServer(adapters, logger);
}
}
}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs b/Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs
new file mode 100644
index 00000000..6bbce0cb
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/IMqttPacketWithIdentifier.cs
@@ -0,0 +1,7 @@
+namespace MQTTnet.Packets
+{
+ public interface IMqttPacketWithIdentifier
+ {
+ ushort? PacketIdentifier { get; set; }
+ }
+}
diff --git a/MQTTnet.Core/Packets/MqttBasePacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs
similarity index 62%
rename from MQTTnet.Core/Packets/MqttBasePacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs
index 41901e52..d9640568 100644
--- a/MQTTnet.Core/Packets/MqttBasePacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttBasePacket.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Packets
+namespace MQTTnet.Packets
{
public abstract class MqttBasePacket
{
diff --git a/MQTTnet.Core/Packets/MqttBasePublishPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs
similarity index 53%
rename from MQTTnet.Core/Packets/MqttBasePublishPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs
index 67d55b90..ffafc537 100644
--- a/MQTTnet.Core/Packets/MqttBasePublishPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttBasePublishPacket.cs
@@ -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; }
}
}
diff --git a/MQTTnet.Core/Packets/MqttConnAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs
similarity index 55%
rename from MQTTnet.Core/Packets/MqttConnAckPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs
index e80a0af5..9cf0adfe 100644
--- a/MQTTnet.Core/Packets/MqttConnAckPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnAckPacket.cs
@@ -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 + "]";
}
}
}
diff --git a/MQTTnet.Core/Packets/MqttConnectPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs
similarity index 64%
rename from MQTTnet.Core/Packets/MqttConnectPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs
index b1d6e3b9..b1943ced 100644
--- a/MQTTnet.Core/Packets/MqttConnectPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttConnectPacket.cs
@@ -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 + "]";
}
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs
new file mode 100644
index 00000000..46c3270d
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttDisconnectPacket.cs
@@ -0,0 +1,10 @@
+namespace MQTTnet.Packets
+{
+ public sealed class MqttDisconnectPacket : MqttBasePacket
+ {
+ public override string ToString()
+ {
+ return "Disconnect";
+ }
+ }
+}
diff --git a/MQTTnet.Core/Packets/MqttPacketHeader.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs
similarity index 77%
rename from MQTTnet.Core/Packets/MqttPacketHeader.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs
index 41646df4..01c26ecd 100644
--- a/MQTTnet.Core/Packets/MqttPacketHeader.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPacketHeader.cs
@@ -1,6 +1,6 @@
-using MQTTnet.Core.Protocol;
+using MQTTnet.Protocol;
-namespace MQTTnet.Core.Packets
+namespace MQTTnet.Packets
{
public class MqttPacketHeader
{
diff --git a/MQTTnet.Core/Packets/MqttPingReqPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs
similarity index 63%
rename from MQTTnet.Core/Packets/MqttPingReqPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs
index 3df1c3ae..fee9926c 100644
--- a/MQTTnet.Core/Packets/MqttPingReqPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingReqPacket.cs
@@ -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";
}
}
}
diff --git a/MQTTnet.Core/Packets/MqttPingRespPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs
similarity index 63%
rename from MQTTnet.Core/Packets/MqttPingRespPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs
index 753a357d..c5f82a3b 100644
--- a/MQTTnet.Core/Packets/MqttPingRespPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPingRespPacket.cs
@@ -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";
}
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs
new file mode 100644
index 00000000..e7fe57b7
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubAckPacket.cs
@@ -0,0 +1,10 @@
+namespace MQTTnet.Packets
+{
+ public sealed class MqttPubAckPacket : MqttBasePublishPacket
+ {
+ public override string ToString()
+ {
+ return "PubAck";
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs
new file mode 100644
index 00000000..98dace70
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubCompPacket.cs
@@ -0,0 +1,10 @@
+namespace MQTTnet.Packets
+{
+ public sealed class MqttPubCompPacket : MqttBasePublishPacket
+ {
+ public override string ToString()
+ {
+ return "PubComp";
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs
new file mode 100644
index 00000000..86cd4b45
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRecPacket.cs
@@ -0,0 +1,10 @@
+namespace MQTTnet.Packets
+{
+ public sealed class MqttPubRecPacket : MqttBasePublishPacket
+ {
+ public override string ToString()
+ {
+ return "PubRec";
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs
new file mode 100644
index 00000000..ed1d933b
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPubRelPacket.cs
@@ -0,0 +1,10 @@
+namespace MQTTnet.Packets
+{
+ public sealed class MqttPubRelPacket : MqttBasePublishPacket
+ {
+ public override string ToString()
+ {
+ return "PubRel";
+ }
+ }
+}
diff --git a/MQTTnet.Core/Packets/MqttPublishPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs
similarity index 71%
rename from MQTTnet.Core/Packets/MqttPublishPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs
index 447aaed7..52fcceed 100644
--- a/MQTTnet.Core/Packets/MqttPublishPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttPublishPacket.cs
@@ -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 + "]" +
diff --git a/MQTTnet.Core/Packets/MqttSubAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs
similarity index 62%
rename from MQTTnet.Core/Packets/MqttSubAckPacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs
index 5882d4c8..3b8cf7a8 100644
--- a/MQTTnet.Core/Packets/MqttSubAckPacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubAckPacket.cs
@@ -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 SubscribeReturnCodes { get; } = new List();
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 + "]";
}
}
}
diff --git a/MQTTnet.Core/Packets/MqttSubscribePacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs
similarity index 66%
rename from MQTTnet.Core/Packets/MqttSubscribePacket.cs
rename to Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs
index 5cf1c260..63aede16 100644
--- a/MQTTnet.Core/Packets/MqttSubscribePacket.cs
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttSubscribePacket.cs
@@ -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 TopicFilters { get; set; } = new List();
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 + "]";
}
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs
new file mode 100644
index 00000000..688195fa
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubAckPacket.cs
@@ -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 + "]";
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs
new file mode 100644
index 00000000..6c24a477
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Packets/MqttUnsubscribe.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace MQTTnet.Packets
+{
+ public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
+ {
+ public ushort? PacketIdentifier { get; set; }
+
+ public IList TopicFilters { get; set; } = new List();
+
+ public override string ToString()
+ {
+ var topicFiltersText = string.Join(",", TopicFilters);
+ return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
+ }
+ }
+}
diff --git a/MQTTnet.Core/Protocol/MqttConnectReturnCode.cs b/Frameworks/MQTTnet.NetStandard/Protocol/MqttConnectReturnCode.cs
similarity index 91%
rename from MQTTnet.Core/Protocol/MqttConnectReturnCode.cs
rename to Frameworks/MQTTnet.NetStandard/Protocol/MqttConnectReturnCode.cs
index 604cd482..bffc4dcf 100644
--- a/MQTTnet.Core/Protocol/MqttConnectReturnCode.cs
+++ b/Frameworks/MQTTnet.NetStandard/Protocol/MqttConnectReturnCode.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Protocol
+namespace MQTTnet.Protocol
{
public enum MqttConnectReturnCode
{
diff --git a/MQTTnet.Core/Protocol/MqttControlPacketType.cs b/Frameworks/MQTTnet.NetStandard/Protocol/MqttControlPacketType.cs
similarity index 91%
rename from MQTTnet.Core/Protocol/MqttControlPacketType.cs
rename to Frameworks/MQTTnet.NetStandard/Protocol/MqttControlPacketType.cs
index aa46b9df..adb58b66 100644
--- a/MQTTnet.Core/Protocol/MqttControlPacketType.cs
+++ b/Frameworks/MQTTnet.NetStandard/Protocol/MqttControlPacketType.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Protocol
+namespace MQTTnet.Protocol
{
public enum MqttControlPacketType
{
diff --git a/MQTTnet.Core/Protocol/MqttQualityOfServiceLevel.cs b/Frameworks/MQTTnet.NetStandard/Protocol/MqttQualityOfServiceLevel.cs
similarity index 80%
rename from MQTTnet.Core/Protocol/MqttQualityOfServiceLevel.cs
rename to Frameworks/MQTTnet.NetStandard/Protocol/MqttQualityOfServiceLevel.cs
index c19496a8..579f72ef 100644
--- a/MQTTnet.Core/Protocol/MqttQualityOfServiceLevel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Protocol/MqttQualityOfServiceLevel.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Protocol
+namespace MQTTnet.Protocol
{
public enum MqttQualityOfServiceLevel
{
diff --git a/MQTTnet.Core/Protocol/MqttSubscribeReturnCode.cs b/Frameworks/MQTTnet.NetStandard/Protocol/MqttSubscribeReturnCode.cs
similarity index 84%
rename from MQTTnet.Core/Protocol/MqttSubscribeReturnCode.cs
rename to Frameworks/MQTTnet.NetStandard/Protocol/MqttSubscribeReturnCode.cs
index a561423f..b256962b 100644
--- a/MQTTnet.Core/Protocol/MqttSubscribeReturnCode.cs
+++ b/Frameworks/MQTTnet.NetStandard/Protocol/MqttSubscribeReturnCode.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Protocol
+namespace MQTTnet.Protocol
{
public enum MqttSubscribeReturnCode
{
diff --git a/MQTTnet.Core/Serializer/ByteReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs
similarity index 96%
rename from MQTTnet.Core/Serializer/ByteReader.cs
rename to Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs
index ce7f709d..ec1e3ea2 100644
--- a/MQTTnet.Core/Serializer/ByteReader.cs
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/ByteReader.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Serializer
+namespace MQTTnet.Serializer
{
public sealed class ByteReader
{
diff --git a/MQTTnet.Core/Serializer/ByteWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs
similarity index 95%
rename from MQTTnet.Core/Serializer/ByteWriter.cs
rename to Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs
index d6abec90..af961e1a 100644
--- a/MQTTnet.Core/Serializer/ByteWriter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/ByteWriter.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Serializer
+namespace MQTTnet.Serializer
{
public sealed class ByteWriter
{
diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs
new file mode 100644
index 00000000..6577b0ad
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/IMqttPacketSerializer.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Serializer
+{
+ public interface IMqttPacketSerializer
+ {
+ MqttProtocolVersion ProtocolVersion { get; set; }
+
+ ICollection> Serialize(MqttBasePacket mqttPacket);
+
+ MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body);
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs
new file mode 100644
index 00000000..bd43f7d3
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketReader.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Exceptions;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
+
+namespace MQTTnet.Serializer
+{
+ public sealed class MqttPacketReader : BinaryReader
+ {
+ private readonly MqttPacketHeader _header;
+
+ public MqttPacketReader(MqttPacketHeader header, Stream bodyStream)
+ : base(bodyStream, Encoding.UTF8, true)
+ {
+ _header = header;
+ }
+
+ public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength;
+
+ public static async Task ReadHeaderAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return null;
+ }
+
+ // Wait for the next package which starts with the header. At this point there will probably
+ // some large delay and thus the thread should be put back to the pool (await). So ReadByte()
+ // is not an option here.
+ var buffer = new byte[1];
+ var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
+ if (readCount <= 0)
+ {
+ return null;
+ }
+
+ var fixedHeader = buffer[0];
+ var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
+ var bodyLength = await ReadBodyLengthAsync(stream, cancellationToken).ConfigureAwait(false);
+
+ return new MqttPacketHeader
+ {
+ FixedHeader = fixedHeader,
+ ControlPacketType = controlPacketType,
+ BodyLength = bodyLength
+ };
+ }
+
+ public override ushort ReadUInt16()
+ {
+ var buffer = ReadBytes(2);
+
+ var temp = buffer[0];
+ buffer[0] = buffer[1];
+ buffer[1] = temp;
+
+ return BitConverter.ToUInt16(buffer, 0);
+ }
+
+ public string ReadStringWithLengthPrefix()
+ {
+ var buffer = ReadWithLengthPrefix();
+ if (buffer.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
+ }
+
+ public byte[] ReadWithLengthPrefix()
+ {
+ var length = ReadUInt16();
+ if (length == 0)
+ {
+ return new byte[0];
+ }
+
+ return ReadBytes(length);
+ }
+
+ public byte[] ReadRemainingData()
+ {
+ return ReadBytes(_header.BodyLength - (int)BaseStream.Position);
+ }
+
+ private static async Task ReadBodyLengthAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ // Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
+ var multiplier = 1;
+ var value = 0;
+ byte encodedByte;
+
+ var buffer = new byte[1];
+ var readBytes = new List();
+ do
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ throw new TaskCanceledException();
+ }
+
+ var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
+ if (readCount <= 0)
+ {
+ throw new MqttCommunicationException("Connection closed while reading remaining length data.");
+ }
+
+ encodedByte = buffer[0];
+ readBytes.Add(encodedByte);
+
+ value += (byte)(encodedByte & 127) * multiplier;
+ if (multiplier > 128 * 128 * 128)
+ {
+ throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)}).");
+ }
+
+ multiplier *= 128;
+ } while ((encodedByte & 128) != 0);
+
+ return value;
+ }
+ }
+}
diff --git a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs
similarity index 78%
rename from MQTTnet.Core/Serializer/MqttPacketSerializer.cs
rename to Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs
index 00906f4a..0904c759 100644
--- a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketSerializer.cs
@@ -1,14 +1,13 @@
-using System;
+using MQTTnet.Exceptions;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Exceptions;
-using MQTTnet.Core.Packets;
-using MQTTnet.Core.Protocol;
-namespace MQTTnet.Core.Serializer
+namespace MQTTnet.Serializer
{
public sealed class MqttPacketSerializer : IMqttPacketSerializer
{
@@ -17,32 +16,40 @@ namespace MQTTnet.Core.Serializer
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
- public byte[] Serialize(MqttBasePacket packet)
+ public ICollection> Serialize(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
- using (var stream = new MemoryStream())
+ using (var stream = new MemoryStream(128))
using (var writer = new MqttPacketWriter(stream))
{
- var header = new List { SerializePacket(packet, writer) };
- var body = stream.ToArray();
- MqttPacketWriter.BuildLengthHeader(body.Length, header);
- var headerArray = header.ToArray();
- var writeBuffer = new byte[header.Count + body.Length];
- Buffer.BlockCopy(headerArray, 0, writeBuffer, 0, headerArray.Length);
- Buffer.BlockCopy(body, 0, writeBuffer, headerArray.Length, body.Length);
+ var fixedHeader = SerializePacket(packet, writer);
+ var remainingLength = (int)stream.Length;
+ writer.Write(fixedHeader);
+ MqttPacketWriter.WriteRemainingLength(remainingLength, writer);
+ var headerLength = (int)stream.Length - remainingLength;
- return writeBuffer;
+#if NET461 || NET452 || NETSTANDARD2_0
+ var buffer = stream.GetBuffer();
+#else
+ var buffer = stream.ToArray();
+#endif
+ return new List>
+ {
+ new ArraySegment(buffer, remainingLength, headerLength),
+ new ArraySegment(buffer, 0, remainingLength)
+ };
}
}
- public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
+ public MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body)
{
- if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
+ if (header == null) throw new ArgumentNullException(nameof(header));
+ if (body == null) throw new ArgumentNullException(nameof(body));
- using (var reader = new MqttPacketReader(receivedMqttPacket))
+ using (var reader = new MqttPacketReader(header, body))
{
- return Deserialize(receivedMqttPacket.Header, reader);
+ return Deserialize(header, reader);
}
}
@@ -68,7 +75,7 @@ namespace MQTTnet.Core.Serializer
}
}
- private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
+ private MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
{
switch (header.ControlPacketType)
{
@@ -271,20 +278,22 @@ namespace MQTTnet.Core.Serializer
return packet;
}
- private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
+ private MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
{
- var variableHeader1 = reader.ReadByte();
- var variableHeader2 = reader.ReadByte();
+ var packet = new MqttConnAckPacket();
- var packet = new MqttConnAckPacket
+ var firstByteReader = new ByteReader(reader.ReadByte());
+
+ if (ProtocolVersion == MqttProtocolVersion.V311)
{
- IsSessionPresent = new ByteReader(variableHeader1).Read(),
- ConnectReturnCode = (MqttConnectReturnCode)variableHeader2
- };
+ packet.IsSessionPresent = firstByteReader.Read();
+ }
+
+ packet.ConnectReturnCode = (MqttConnectReturnCode)reader.ReadByte();
return packet;
}
-
+
private static void ValidateConnectPacket(MqttConnectPacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
@@ -366,14 +375,21 @@ namespace MQTTnet.Core.Serializer
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer)
{
- var connectAcknowledgeFlags = new ByteWriter();
-
- if (ProtocolVersion == MqttProtocolVersion.V311)
+ if (ProtocolVersion == MqttProtocolVersion.V310)
{
+ writer.Write(0);
+ }
+ else if (ProtocolVersion == MqttProtocolVersion.V311)
+ {
+ var connectAcknowledgeFlags = new ByteWriter();
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
+ writer.Write(connectAcknowledgeFlags);
+ }
+ else
+ {
+ throw new MqttProtocolViolationException("Protocol version not supported.");
}
- writer.Write(connectAcknowledgeFlags);
writer.Write((byte)packet.ConnectReturnCode);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
@@ -381,7 +397,12 @@ namespace MQTTnet.Core.Serializer
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("PubRel packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
}
@@ -394,7 +415,12 @@ namespace MQTTnet.Core.Serializer
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("Publish packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
}
else
{
@@ -428,21 +454,36 @@ namespace MQTTnet.Core.Serializer
private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("PubAck packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
}
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("PubRec packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
}
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("PubComp packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
}
@@ -451,7 +492,12 @@ namespace MQTTnet.Core.Serializer
{
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("Subscribe packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
if (packet.TopicFilters?.Count > 0)
{
@@ -467,7 +513,12 @@ namespace MQTTnet.Core.Serializer
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("SubAck packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
if (packet.SubscribeReturnCodes?.Any() == true)
{
@@ -484,7 +535,12 @@ namespace MQTTnet.Core.Serializer
{
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("Unsubscribe packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
if (packet.TopicFilters?.Any() == true)
{
@@ -497,9 +553,14 @@ namespace MQTTnet.Core.Serializer
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
}
- private static byte Serialize(IMqttPacketWithIdentifier packet, BinaryWriter writer)
+ private static byte Serialize(MqttUnsubAckPacket packet, BinaryWriter writer)
{
- writer.Write(packet.PacketIdentifier);
+ if (!packet.PacketIdentifier.HasValue)
+ {
+ throw new MqttProtocolViolationException("UnsubAck packet has no packet identifier.");
+ }
+
+ writer.Write(packet.PacketIdentifier.Value);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
}
diff --git a/MQTTnet.Core/Serializer/MqttPacketWriter.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs
similarity index 86%
rename from MQTTnet.Core/Serializer/MqttPacketWriter.cs
rename to Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs
index d8c997d2..cb3d4589 100644
--- a/MQTTnet.Core/Serializer/MqttPacketWriter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttPacketWriter.cs
@@ -1,15 +1,14 @@
using System;
using System.IO;
using System.Text;
-using MQTTnet.Core.Protocol;
-using System.Collections.Generic;
+using MQTTnet.Protocol;
-namespace MQTTnet.Core.Serializer
+namespace MQTTnet.Serializer
{
public sealed class MqttPacketWriter : BinaryWriter
{
public MqttPacketWriter(Stream stream)
- : base(stream)
+ : base(stream, Encoding.UTF8, true)
{
}
@@ -56,11 +55,11 @@ namespace MQTTnet.Core.Serializer
Write(value);
}
- public static void BuildLengthHeader(int length, List header)
+ public static void WriteRemainingLength(int length, BinaryWriter target)
{
if (length == 0)
{
- header.Add(0);
+ target.Write((byte)0);
return;
}
@@ -75,7 +74,7 @@ namespace MQTTnet.Core.Serializer
encodedByte = encodedByte | 128;
}
- header.Add((byte)encodedByte);
+ target.Write((byte)encodedByte);
} while (x > 0);
}
}
diff --git a/MQTTnet.Core/Serializer/MqttProtocolVersion.cs b/Frameworks/MQTTnet.NetStandard/Serializer/MqttProtocolVersion.cs
similarity index 68%
rename from MQTTnet.Core/Serializer/MqttProtocolVersion.cs
rename to Frameworks/MQTTnet.NetStandard/Serializer/MqttProtocolVersion.cs
index 6b8814a7..15e698d3 100644
--- a/MQTTnet.Core/Serializer/MqttProtocolVersion.cs
+++ b/Frameworks/MQTTnet.NetStandard/Serializer/MqttProtocolVersion.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Serializer
+namespace MQTTnet.Serializer
{
public enum MqttProtocolVersion
{
diff --git a/Frameworks/MQTTnet.NetStandard/Server/CheckSubscriptionsResult.cs b/Frameworks/MQTTnet.NetStandard/Server/CheckSubscriptionsResult.cs
new file mode 100644
index 00000000..9e2d1f0d
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/CheckSubscriptionsResult.cs
@@ -0,0 +1,11 @@
+using MQTTnet.Protocol;
+
+namespace MQTTnet.Server
+{
+ public class CheckSubscriptionsResult
+ {
+ public bool IsSubscribed { get; set; }
+
+ public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs b/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs
new file mode 100644
index 00000000..4fa8e4ea
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/ConnectedMqttClient.cs
@@ -0,0 +1,18 @@
+using System;
+using MQTTnet.Serializer;
+
+namespace MQTTnet.Server
+{
+ public class ConnectedMqttClient
+ {
+ public string ClientId { get; set; }
+
+ public MqttProtocolVersion ProtocolVersion { get; set; }
+
+ public TimeSpan LastPacketReceived { get; set; }
+
+ public TimeSpan LastNonKeepAlivePacketReceived { get; set; }
+
+ public int PendingApplicationMessages { get; set; }
+ }
+}
diff --git a/MQTTnet.Core/Server/GetOrCreateClientSessionResult.cs b/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs
similarity index 84%
rename from MQTTnet.Core/Server/GetOrCreateClientSessionResult.cs
rename to Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs
index 8f802720..8f5f085e 100644
--- a/MQTTnet.Core/Server/GetOrCreateClientSessionResult.cs
+++ b/Frameworks/MQTTnet.NetStandard/Server/GetOrCreateClientSessionResult.cs
@@ -1,4 +1,4 @@
-namespace MQTTnet.Core.Server
+namespace MQTTnet.Server
{
public sealed class GetOrCreateClientSessionResult
{
diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs
new file mode 100644
index 00000000..229e79c5
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServer.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Server
+{
+ public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher
+ {
+ event EventHandler Started;
+
+ event EventHandler ClientConnected;
+ event EventHandler ClientDisconnected;
+ event EventHandler ClientSubscribedTopic;
+ event EventHandler ClientUnsubscribedTopic;
+
+ IMqttServerOptions Options { get; }
+
+ Task> GetConnectedClientsAsync();
+
+ Task SubscribeAsync(string clientId, IList topicFilters);
+ Task UnsubscribeAsync(string clientId, IList topicFilters);
+
+ Task StartAsync(IMqttServerOptions options);
+ Task StopAsync();
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerFactory.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerFactory.cs
new file mode 100644
index 00000000..6e399cae
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerFactory.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using MQTTnet.Adapter;
+using MQTTnet.Diagnostics;
+
+namespace MQTTnet.Server
+{
+ public interface IMqttServerFactory
+ {
+ IMqttServer CreateMqttServer();
+
+ IMqttServer CreateMqttServer(IEnumerable adapters, IMqttNetLogger logger);
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs
new file mode 100644
index 00000000..cc58eeb8
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerOptions.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace MQTTnet.Server
+{
+ public interface IMqttServerOptions
+ {
+ int ConnectionBacklog { get; }
+ TimeSpan DefaultCommunicationTimeout { get; }
+
+ Action ConnectionValidator { get; }
+ Action SubscriptionInterceptor { get; }
+ Action ApplicationMessageInterceptor { get; }
+
+ MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; }
+ MqttServerTlsEndpointOptions TlsEndpointOptions { get; }
+
+ IMqttServerStorage Storage { get; }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Server/IMqttServerStorage.cs b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerStorage.cs
similarity index 90%
rename from MQTTnet.Core/Server/IMqttServerStorage.cs
rename to Frameworks/MQTTnet.NetStandard/Server/IMqttServerStorage.cs
index 3cb518c3..da6e6e28 100644
--- a/MQTTnet.Core/Server/IMqttServerStorage.cs
+++ b/Frameworks/MQTTnet.NetStandard/Server/IMqttServerStorage.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace MQTTnet.Core.Server
+namespace MQTTnet.Server
{
public interface IMqttServerStorage
{
diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttApplicationMessageInterceptorContext.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttApplicationMessageInterceptorContext.cs
new file mode 100644
index 00000000..56126015
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttApplicationMessageInterceptorContext.cs
@@ -0,0 +1,19 @@
+namespace MQTTnet.Server
+{
+ public class MqttApplicationMessageInterceptorContext
+ {
+ public MqttApplicationMessageInterceptorContext(string clientId, MqttApplicationMessage applicationMessage)
+ {
+ ClientId = clientId;
+ ApplicationMessage = applicationMessage;
+ }
+
+ public string ClientId { get; }
+
+ public MqttApplicationMessage ApplicationMessage { get; set; }
+
+ public bool AcceptPublish { get; set; } = true;
+
+ public bool CloseConnection { get; set; }
+ }
+}
diff --git a/MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs
similarity index 91%
rename from MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs
rename to Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs
index 282e0f8d..1ca3f406 100644
--- a/MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientConnectedEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace MQTTnet.Core.Server
+namespace MQTTnet.Server
{
public class MqttClientConnectedEventArgs : EventArgs
{
diff --git a/MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs
similarity index 66%
rename from MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs
rename to Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs
index b1e3d15b..6dd505e5 100644
--- a/MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientDisconnectedEventArgs.cs
@@ -1,14 +1,17 @@
using System;
-namespace MQTTnet.Core.Server
+namespace MQTTnet.Server
{
public class MqttClientDisconnectedEventArgs : EventArgs
{
- public MqttClientDisconnectedEventArgs(ConnectedMqttClient client)
+ public MqttClientDisconnectedEventArgs(ConnectedMqttClient client, bool wasCleanDisconnect)
{
Client = client ?? throw new ArgumentNullException(nameof(client));
+ WasCleanDisconnect = wasCleanDisconnect;
}
public ConnectedMqttClient Client { get; }
+
+ public bool WasCleanDisconnect { get; }
}
}
diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs
new file mode 100644
index 00000000..3f215369
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientKeepAliveMonitor.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Diagnostics;
+using MQTTnet.Packets;
+
+namespace MQTTnet.Server
+{
+ public sealed class MqttClientKeepAliveMonitor
+ {
+ private readonly Stopwatch _lastPacketReceivedTracker = new Stopwatch();
+ private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
+
+ private readonly string _clientId;
+ private readonly Func _timeoutCallback;
+ private readonly IMqttNetLogger _logger;
+
+ private Task _workerTask;
+
+ public MqttClientKeepAliveMonitor(string clientId, Func timeoutCallback, IMqttNetLogger logger)
+ {
+ _clientId = clientId;
+ _timeoutCallback = timeoutCallback;
+ _logger = logger;
+ }
+
+ public TimeSpan LastPacketReceived => _lastPacketReceivedTracker.Elapsed;
+
+ public TimeSpan LastNonKeepAlivePacketReceived => _lastNonKeepAlivePacketReceivedTracker.Elapsed;
+
+ public void Start(int keepAlivePeriod, CancellationToken cancellationToken)
+ {
+ if (keepAlivePeriod == 0)
+ {
+ return;
+ }
+
+ _workerTask = Task.Run(() => RunAsync(keepAlivePeriod, cancellationToken).ConfigureAwait(false), cancellationToken);
+ }
+
+ public void WaitForCompletion()
+ {
+ if (_workerTask != null)
+ {
+ Task.WaitAll(_workerTask);
+ }
+ }
+
+ private async Task RunAsync(int keepAlivePeriod, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _lastPacketReceivedTracker.Restart();
+ _lastNonKeepAlivePacketReceivedTracker.Restart();
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ // Values described here: [MQTT-3.1.2-24].
+ if (_lastPacketReceivedTracker.Elapsed.TotalSeconds > keepAlivePeriod * 1.5D)
+ {
+ _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
+
+ if (_timeoutCallback != null)
+ {
+ await _timeoutCallback().ConfigureAwait(false);
+ }
+
+ return;
+ }
+
+ await Task.Delay(keepAlivePeriod, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(exception, "Client '{0}': Unhandled exception while checking keep alive timeouts.", _clientId);
+ }
+ finally
+ {
+ _logger.Verbose("Client {0}: Stopped checking keep alive timeout.", _clientId);
+ }
+ }
+
+ public void PacketReceived(MqttBasePacket packet)
+ {
+ _lastPacketReceivedTracker.Restart();
+
+ if (!(packet is MqttPingReqPacket))
+ {
+ _lastNonKeepAlivePacketReceivedTracker.Restart();
+ }
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs
new file mode 100644
index 00000000..99493874
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientPendingMessagesQueue.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Adapter;
+using MQTTnet.Diagnostics;
+using MQTTnet.Exceptions;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
+
+namespace MQTTnet.Server
+{
+ public sealed class MqttClientPendingMessagesQueue : IDisposable
+ {
+ private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0);
+ private readonly IMqttServerOptions _options;
+ private readonly MqttClientSession _clientSession;
+ private readonly IMqttNetLogger _logger;
+
+ private Task _workerTask;
+
+ public MqttClientPendingMessagesQueue(IMqttServerOptions options, MqttClientSession clientSession, IMqttNetLogger logger)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public int Count => _queue.Count;
+
+ public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
+ {
+ if (adapter == null) throw new ArgumentNullException(nameof(adapter));
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ _workerTask = Task.Run(() => SendQueuedPacketsAsync(adapter, cancellationToken), cancellationToken);
+ }
+
+ public void WaitForCompletion()
+ {
+ if (_workerTask != null)
+ {
+ Task.WaitAll(_workerTask);
+ }
+ }
+
+ public void Enqueue(MqttBasePacket packet)
+ {
+ if (packet == null) throw new ArgumentNullException(nameof(packet));
+
+ _queue.Enqueue(packet);
+ _queueWaitSemaphore.Release();
+
+ _logger.Verbose("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
+ }
+
+ private async Task SendQueuedPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
+ {
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await SendNextQueuedPacketAsync(adapter, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _clientSession.ClientId);
+ }
+ }
+
+ private async Task SendNextQueuedPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
+ {
+ MqttBasePacket packet = null;
+ try
+ {
+ await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ if (!_queue.TryDequeue(out packet))
+ {
+ throw new InvalidOperationException(); // should not happen
+ }
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false);
+
+ _logger.Verbose("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
+ }
+ catch (Exception exception)
+ {
+ if (exception is MqttCommunicationTimedOutException)
+ {
+ _logger.Warning(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _clientSession.ClientId);
+ }
+ else if (exception is MqttCommunicationException)
+ {
+ _logger.Warning(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _clientSession.ClientId);
+ }
+ else if (exception is OperationCanceledException)
+ {
+ }
+ else
+ {
+ _logger.Error(exception, "Sending publish packet failed (ClientId: {0}).", _clientSession.ClientId);
+ }
+
+ if (packet is MqttPublishPacket publishPacket)
+ {
+ if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
+ {
+ publishPacket.Dup = true;
+ _queue.Enqueue(packet);
+ _queueWaitSemaphore.Release();
+ }
+ }
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ await _clientSession.StopAsync().ConfigureAwait(false);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _queueWaitSemaphore?.Dispose();
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs
new file mode 100644
index 00000000..8514da99
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSession.cs
@@ -0,0 +1,346 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Adapter;
+using MQTTnet.Client;
+using MQTTnet.Diagnostics;
+using MQTTnet.Exceptions;
+using MQTTnet.Internal;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
+using MQTTnet.Serializer;
+
+namespace MQTTnet.Server
+{
+ public sealed class MqttClientSession : IDisposable
+ {
+ private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
+ private readonly IMqttServerOptions _options;
+ private readonly IMqttNetLogger _logger;
+ private readonly MqttRetainedMessagesManager _retainedMessagesManager;
+
+ private IMqttChannelAdapter _adapter;
+ private CancellationTokenSource _cancellationTokenSource;
+ private MqttApplicationMessage _willMessage;
+ private bool _wasCleanDisconnect;
+
+ public MqttClientSession(
+ string clientId,
+ IMqttServerOptions options,
+ MqttRetainedMessagesManager retainedMessagesManager,
+ IMqttNetLogger logger)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+
+ ClientId = clientId;
+
+ KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger);
+ SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId);
+ PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
+ }
+
+ public Func ApplicationMessageReceivedCallback { get; set; }
+
+ public MqttClientSubscriptionsManager SubscriptionsManager { get; }
+
+ public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
+
+ public MqttClientKeepAliveMonitor KeepAliveMonitor { get; }
+
+ public string ClientId { get; }
+
+ public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
+
+ public bool IsConnected => _adapter != null;
+
+ public async Task RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
+ {
+ if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
+ if (adapter == null) throw new ArgumentNullException(nameof(adapter));
+
+ try
+ {
+ var cancellationTokenSource = new CancellationTokenSource();
+
+ _wasCleanDisconnect = false;
+ _willMessage = connectPacket.WillMessage;
+ _adapter = adapter;
+ _cancellationTokenSource = cancellationTokenSource;
+
+ PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
+ KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token);
+
+ await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (MqttCommunicationException exception)
+ {
+ _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
+ }
+
+ return _wasCleanDisconnect;
+ }
+
+ public async Task StopAsync(bool wasCleanDisconnect = false)
+ {
+ try
+ {
+ if (_cancellationTokenSource == null)
+ {
+ return;
+ }
+
+ _wasCleanDisconnect = wasCleanDisconnect;
+
+ _cancellationTokenSource?.Cancel(false);
+
+ PendingMessagesQueue.WaitForCompletion();
+ KeepAliveMonitor.WaitForCompletion();
+
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+
+ _adapter = null;
+
+ _logger.Info("Client '{0}': Session stopped.", ClientId);
+ }
+ finally
+ {
+ var willMessage = _willMessage;
+ _willMessage = null; // clear willmessage so it is send just once
+
+ if (willMessage != null && !wasCleanDisconnect)
+ {
+ await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false);
+ }
+ }
+ }
+
+ public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
+ {
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+
+ var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage);
+ if (!result.IsSubscribed)
+ {
+ return;
+ }
+
+ var publishPacket = applicationMessage.ToPublishPacket();
+ publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
+
+ if (publishPacket.QualityOfServiceLevel > 0)
+ {
+ publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
+ }
+
+ PendingMessagesQueue.Enqueue(publishPacket);
+ }
+
+ public async Task SubscribeAsync(IList topicFilters)
+ {
+ if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+
+ await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket
+ {
+ TopicFilters = topicFilters
+ }).ConfigureAwait(false);
+
+ await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
+ }
+
+ public Task UnsubscribeAsync(IList topicFilters)
+ {
+ if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+
+ return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket
+ {
+ TopicFilters = topicFilters
+ });
+ }
+
+ public void Dispose()
+ {
+ ApplicationMessageReceivedCallback = null;
+
+ SubscriptionsManager?.Dispose();
+ PendingMessagesQueue?.Dispose();
+
+ _cancellationTokenSource?.Dispose();
+ }
+
+ private Task StopDueToKeepAliveTimeoutAsync()
+ {
+ _logger.Info("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
+ return StopAsync();
+ }
+
+ private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
+ {
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
+ KeepAliveMonitor.PacketReceived(packet);
+ await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (MqttCommunicationException exception)
+ {
+ _logger.Warning(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
+ await StopAsync().ConfigureAwait(false);
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
+ await StopAsync().ConfigureAwait(false);
+ }
+ }
+
+ private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
+ {
+ if (packet is MqttPublishPacket publishPacket)
+ {
+ return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken);
+ }
+
+ if (packet is MqttPingReqPacket)
+ {
+ return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() });
+ }
+
+ if (packet is MqttPubRelPacket pubRelPacket)
+ {
+ return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken);
+ }
+
+ if (packet is MqttPubRecPacket pubRecPacket)
+ {
+ var responsePacket = new MqttPubRelPacket
+ {
+ PacketIdentifier = pubRecPacket.PacketIdentifier
+ };
+
+ return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket });
+ }
+
+ if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
+ {
+ // Discard message.
+ return Task.FromResult(0);
+ }
+
+ if (packet is MqttSubscribePacket subscribePacket)
+ {
+ return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken);
+ }
+
+ if (packet is MqttUnsubscribePacket unsubscribePacket)
+ {
+ return HandleIncomingUnsubscribePacketAsync(adapter, unsubscribePacket, cancellationToken);
+ }
+
+ if (packet is MqttDisconnectPacket)
+ {
+ return StopAsync(true);
+ }
+
+ if (packet is MqttConnectPacket)
+ {
+ return StopAsync();
+ }
+
+ _logger.Warning("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
+ return StopAsync();
+ }
+
+ private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
+ {
+ var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
+
+ if (subscribeResult.CloseConnection)
+ {
+ await StopAsync().ConfigureAwait(false);
+ }
+
+ await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
+ }
+
+ private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
+ {
+ var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult });
+ }
+
+ private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters)
+ {
+ var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters);
+ foreach (var applicationMessage in retainedMessages)
+ {
+ await EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
+ }
+ }
+
+ private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ var applicationMessage = publishPacket.ToApplicationMessage();
+
+ switch (applicationMessage.QualityOfServiceLevel)
+ {
+ case MqttQualityOfServiceLevel.AtMostOnce:
+ {
+ return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage);
+ }
+ case MqttQualityOfServiceLevel.AtLeastOnce:
+ {
+ return HandleIncomingPublishPacketWithQoS1(adapter, applicationMessage, publishPacket, cancellationToken);
+ }
+ case MqttQualityOfServiceLevel.ExactlyOnce:
+ {
+ return HandleIncomingPublishPacketWithQoS2(adapter, applicationMessage, publishPacket, cancellationToken);
+ }
+ default:
+ {
+ throw new MqttCommunicationException("Received a not supported QoS level.");
+ }
+ }
+ }
+
+ private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
+
+ var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
+ }
+
+ private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
+ {
+ // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
+ await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false);
+
+ var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false);
+ }
+
+ private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
+ {
+ var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
+ return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response });
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs
new file mode 100644
index 00000000..8dd9d062
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/Server/MqttClientSessionsManager.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MQTTnet.Adapter;
+using MQTTnet.Diagnostics;
+using MQTTnet.Exceptions;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
+using MQTTnet.Serializer;
+
+namespace MQTTnet.Server
+{
+ public sealed class MqttClientSessionsManager : IDisposable
+ {
+ private readonly Dictionary _sessions = new Dictionary();
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+
+ private readonly IMqttServerOptions _options;
+ private readonly MqttRetainedMessagesManager _retainedMessagesManager;
+ private readonly IMqttNetLogger _logger;
+
+ public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
+ }
+
+ public Action ClientConnectedCallback { get; set; }
+ public Action ClientDisconnectedCallback { get; set; }
+ public Action ClientSubscribedTopicCallback { get; set; }
+ public Action