diff --git a/.gitignore b/.gitignore
index b665d097..39550dc5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -289,3 +289,5 @@ __pycache__/
Build/nuget.exe
+*.js
+*.map
diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec
index d6f72810..1ed11881 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -2,28 +2,26 @@
MQTTnet
- 2.4.0
+ 2.5.0
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
- MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).
- * [Server] Added an event which is fired when a client has disconnected
-* [Server] Added support for retained application messages
-* [Server] Added support for saving and loading retained messages
-* [Server] The client connection is now closed if sending of one pending application message has failed
-* [Server] Fixed handling of _Dup_ flag (Thanks to haeberle)
-* [Core] Optimized exception handling
-* [Core] Mono is now also supported (Thanks to JTrotta)
-* [Client] The options are now passed in _ConnectAsync_ (Breaking change! Read Wiki for examples)
-* [Core] Trace class renamed to _MqttNetTrace_ (Breaking change!)
-* [Client] Extended certificate validation options (Breaking change!)
-* [Client] Added static certificate validation callback (NetFramework, NetStandard) / ignorable certificate errors (UniversalWindows) to _MqttTcpChannel_
+ 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
+* [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)
+* [Server] Added support for WebSockets via ASP.NET Core 2.0 (Thanks to @ChristianRiedl)
+* [Server] Added support for a custom application message interceptor
Copyright Christian Kratky 2016-2017
- MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M
+ MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation
@@ -48,8 +46,8 @@
-
-
+
+
\ No newline at end of file
diff --git a/Build/build.ps1 b/Build/build.ps1
index b8e71b13..70b616b9 100644
--- a/Build/build.ps1
+++ b/Build/build.ps1
@@ -3,8 +3,7 @@ param([string]$version)
if ([string]::IsNullOrEmpty($version)) {$version = "0.0.1"}
$msbuild = "MSBuild.exe"
-&$msbuild ..\Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj /t:Build /p:Configuration="Release"
-&$msbuild ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj /t:Build /p:Configuration="Release"
+&dotnet build ..\Frameworks\MQTTnet.Netstandard\MQTTnet.Netstandard.csproj -c="Release"
&$msbuild ..\Frameworks\MQTTnet.UniversalWindows\MQTTnet.UniversalWindows.csproj /t:Build /p:Configuration="Release"
Remove-Item .\NuGet -Force -Recurse
diff --git a/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
new file mode 100644
index 00000000..87317a73
--- /dev/null
+++ b/Frameworks/MQTTnet.AspnetCore/ApplicationBuilderExtensions.cs
@@ -0,0 +1,42 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using MQTTnet.AspNetCore;
+using MQTTnet.Core.Server;
+
+namespace MQTTnet.AspnetCore
+{
+ public static class ApplicationBuilderExtensions
+ {
+ public static IApplicationBuilder UseMqttEndpoint(this IApplicationBuilder app, string path = "/mqtt")
+ {
+ app.UseWebSockets();
+ app.Use(async (context, next) =>
+ {
+ if (context.Request.Path == path && context.WebSockets.IsWebSocketRequest)
+ {
+ var adapter = app.ApplicationServices.GetRequiredService();
+ using (var webSocket = await context.WebSockets.AcceptWebSocketAsync("mqtt"))
+ {
+ await adapter.AcceptWebSocketAsync(webSocket);
+ }
+ }
+ else
+ {
+ await next();
+ }
+ });
+
+ return app;
+ }
+
+ public static IApplicationBuilder UseMqttServer(this IApplicationBuilder app, Action configure)
+ {
+ var server = app.ApplicationServices.GetRequiredService();
+
+ configure(server);
+
+ return app;
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj
new file mode 100644
index 00000000..5142cc15
--- /dev/null
+++ b/Frameworks/MQTTnet.AspnetCore/MQTTnet.AspnetCore.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs
new file mode 100644
index 00000000..b5326ab1
--- /dev/null
+++ b/Frameworks/MQTTnet.AspnetCore/MqttHostedServer.cs
@@ -0,0 +1,29 @@
+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;
+
+namespace MQTTnet.AspnetCore
+{
+ public class MqttHostedServer : MqttServer, IHostedService
+ {
+ public MqttHostedServer(IOptions options, IEnumerable adapters, ILogger logger, MqttClientSessionsManager clientSessionsManager)
+ : base(options, adapters, logger, clientSessionsManager)
+ {
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ return StartAsync();
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return StopAsync();
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
new file mode 100644
index 00000000..5cd5d555
--- /dev/null
+++ b/Frameworks/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs
@@ -0,0 +1,93 @@
+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;
+
+namespace MQTTnet.AspNetCore
+{
+ public 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)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync()
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task AcceptWebSocketAsync(WebSocket webSocket)
+ {
+ if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
+
+ var channel = new MqttWebSocketServerChannel(webSocket);
+ var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(channel);
+
+ var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
+ ClientAccepted?.Invoke(this, eventArgs);
+ return eventArgs.SessionTask;
+ }
+
+ public void Dispose()
+ {
+ 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/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..b0297b21
--- /dev/null
+++ b/Frameworks/MQTTnet.AspnetCore/ServiceCollectionExtensions.cs
@@ -0,0 +1,25 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using MQTTnet.AspNetCore;
+using MQTTnet.Core.Adapter;
+using MQTTnet.Core.Server;
+
+namespace MQTTnet.AspnetCore
+{
+ public static class ServiceCollectionExtensions
+ {
+ public static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
+ {
+ services.AddMqttServerServices();
+
+ services.AddSingleton(s => s.GetService());
+ services.AddSingleton(s => s.GetService());
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton(s => s.GetService());
+
+ return services;
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs
deleted file mode 100644
index 799ccddd..00000000
--- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttCommunicationAdapterFactory.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Client;
-using MQTTnet.Core.Serializer;
-
-namespace MQTTnet.Implementations
-{
- public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
- {
- public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- if (options is MqttClientTcpOptions tcpOptions)
- {
- return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
- }
-
- if (options is MqttClientWebSocketOptions webSocketOptions)
- {
- return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
- }
-
- throw new NotSupportedException();
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs
deleted file mode 100644
index c6df6efc..00000000
--- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Security;
-using System.Net.Sockets;
-using System.Security.Authentication;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading;
-using System.Threading.Tasks;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
-using MQTTnet.Core.Serializer;
-using MQTTnet.Core.Server;
-
-namespace MQTTnet.Implementations
-{
- public class MqttServerAdapter : IMqttServerAdapter, IDisposable
- {
- private CancellationTokenSource _cancellationTokenSource;
- private Socket _defaultEndpointSocket;
- private Socket _tlsEndpointSocket;
- private X509Certificate2 _tlsCertificate;
-
- private bool _isRunning;
-
- public event Action ClientAccepted;
-
- public Task StartAsync(MqttServerOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- if (_isRunning) throw new InvalidOperationException("Server is already started.");
- _isRunning = true;
-
- _cancellationTokenSource = new CancellationTokenSource();
-
- if (options.DefaultEndpointOptions.IsEnabled)
- {
- _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
- _defaultEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetDefaultEndpointPort()));
- _defaultEndpointSocket.Listen(options.ConnectionBacklog);
-
- Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
- }
-
- if (options.TlsEndpointOptions.IsEnabled)
- {
- if (options.TlsEndpointOptions.Certificate == null)
- {
- throw new ArgumentException("TLS certificate is not set.");
- }
-
- _tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
-
- _tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
- _tlsEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetTlsEndpointPort()));
- _tlsEndpointSocket.Listen(options.ConnectionBacklog);
-
- Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
- }
-
- return Task.FromResult(0);
- }
-
- public Task StopAsync()
- {
- _isRunning = false;
-
- _cancellationTokenSource?.Cancel(false);
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
-
- _defaultEndpointSocket?.Dispose();
- _defaultEndpointSocket = null;
-
- _tlsEndpointSocket?.Dispose();
- _tlsEndpointSocket = null;
-
- return Task.FromResult(0);
- }
-
- public void Dispose()
- {
- StopAsync();
- }
-
- private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- try
- {
- var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
-
- var tcpChannel = new MqttTcpChannel(clientSocket, null);
- var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
- ClientAccepted?.Invoke(clientAdapter);
- }
- catch (Exception exception) when (!(exception is ObjectDisposedException))
- {
- MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
-
- //excessive CPU consumed if in endless loop of socket errors
- await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
- }
- }
- }
-
- private async Task AcceptTlsEndpointConnectionsAsync(CancellationToken cancellationToken)
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- try
- {
- var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false);
-
- var sslStream = new SslStream(new NetworkStream(clientSocket));
- await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
-
- var tcpChannel = new MqttTcpChannel(clientSocket, sslStream);
- var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
- ClientAccepted?.Invoke(clientAdapter);
- }
- catch (Exception exception)
- {
- MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
-
- //excessive CPU consumed if in endless loop of socket errors
- await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs
deleted file mode 100644
index 6f9375ef..00000000
--- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs
+++ /dev/null
@@ -1,150 +0,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;
-
-namespace MQTTnet.Implementations
-{
- public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
- {
- private readonly MqttClientTcpOptions _options;
-
- // 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 Socket _socket;
- private SslStream _sslStream;
-
- ///
- /// called on client sockets are created in connect
- ///
- public MqttTcpChannel(MqttClientTcpOptions options)
- {
- _options = options ?? throw new ArgumentNullException(nameof(options));
- }
-
- ///
- /// called on server, sockets are passed in
- /// connect will not be called
- ///
- public MqttTcpChannel(Socket socket, SslStream sslStream)
- {
- _socket = socket ?? throw new ArgumentNullException(nameof(socket));
- _sslStream = sslStream;
-
- CreateStreams(socket, sslStream);
- }
-
- public Stream SendStream { get; private set; }
- public Stream ReceiveStream { get; private set; }
- public Stream RawReceiveStream { get; private set; }
-
- public static Func CustomCertificateValidationCallback { get; set; }
-
- public async Task ConnectAsync()
- {
- if (_socket == null)
- {
- _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
- }
-
- await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
-
- 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);
- }
-
- CreateStreams(_socket, _sslStream);
- }
-
- public Task DisconnectAsync()
- {
- Dispose();
- return Task.FromResult(0);
- }
-
- public void Dispose()
- {
- RawReceiveStream?.Dispose();
- RawReceiveStream = null;
-
- ReceiveStream?.Dispose();
- ReceiveStream = null;
-
- SendStream?.Dispose();
- SendStream = null;
-
- _socket?.Dispose();
- _socket = null;
-
- _sslStream?.Dispose();
- _sslStream = null;
- }
-
- private void CreateStreams(Socket socket, Stream sslStream)
- {
- 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
- SendStream = new BufferedStream(RawReceiveStream, BufferSize);
- ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize);
- }
-
- private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
- {
- if (CustomCertificateValidationCallback != null)
- {
- return CustomCertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _options);
- }
-
- if (sslPolicyErrors == SslPolicyErrors.None)
- {
- return true;
- }
-
- if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.RevocationStatusUnknown))
- {
- if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
- {
- return false;
- }
- }
-
- if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain))
- {
- if (!_options.TlsOptions.IgnoreCertificateChainErrors)
- {
- return false;
- }
- }
-
- return _options.TlsOptions.AllowUntrustedCertificates;
- }
-
- private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
- {
- var certificates = new X509CertificateCollection();
- if (options.TlsOptions.Certificates == null)
- {
- return certificates;
- }
-
- foreach (var certificate in options.TlsOptions.Certificates)
- {
- certificates.Add(new X509Certificate(certificate));
- }
-
- return certificates;
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs
deleted file mode 100644
index d6c92ac9..00000000
--- a/Frameworks/MQTTnet.NetFramework/Implementations/MqttWebSocketChannel.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using MQTTnet.Core.Channel;
-using MQTTnet.Core.Client;
-using System;
-using System.IO;
-using System.Net.WebSockets;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MQTTnet.Implementations
-{
- public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable
- {
- private readonly MqttClientWebSocketOptions _options;
-
- private ClientWebSocket _webSocket;
-
- public MqttWebSocketChannel(MqttClientWebSocketOptions options)
- {
- _options = options ?? throw new ArgumentNullException(nameof(options));
- }
-
- public Stream RawReceiveStream { get; private set; }
- public Stream SendStream => RawReceiveStream;
- public Stream ReceiveStream => RawReceiveStream;
-
- public async Task ConnectAsync()
- {
- var uri = _options.Uri;
- if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
- {
- uri = "ws://" + uri;
- }
-
- _webSocket = new ClientWebSocket();
- _webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod;
- await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
- RawReceiveStream = new WebSocketStream(_webSocket);
- }
-
- public async Task DisconnectAsync()
- {
- RawReceiveStream = null;
-
- if (_webSocket == null)
- {
- return;
- }
-
- await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
- }
-
- public void Dispose()
- {
- _webSocket?.Dispose();
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs
deleted file mode 100644
index 4f059360..00000000
--- a/Frameworks/MQTTnet.NetFramework/Implementations/WebSocketStream.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.IO;
-using System.Net.WebSockets;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MQTTnet.Implementations
-{
- public class WebSocketStream : Stream
- {
- private readonly ClientWebSocket _webSocket;
-
- public WebSocketStream(ClientWebSocket webSocket)
- {
- _webSocket = webSocket;
- }
-
- public override void Flush()
- {
- }
-
- public override async Task ReadAsync(byte[] buffer, int offset, int count, 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;
-
- if (response.MessageType == WebSocketMessageType.Close)
- {
- await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
- }
- }
-
- 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);
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
- }
-
- public override bool CanRead => true;
- public override bool CanSeek => false;
- public override bool CanWrite => true;
-
- public override long Length => throw new NotSupportedException();
-
- public override long Position
- {
- get => throw new NotSupportedException();
- set => throw new NotSupportedException();
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
- }
-}
diff --git a/Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj b/Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj
deleted file mode 100644
index 22d50325..00000000
--- a/Frameworks/MQTTnet.NetFramework/MQTTnet.NetFramework.csproj
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}
- Library
- Properties
- MQTTnet
- MQTTnet
- v4.5
- 512
-
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- false
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- false
-
-
- bin\Any CPU\
- TRACE
- true
- pdbonly
- AnyCPU
- prompt
- MinimumRecommendedRules.ruleset
-
-
- true
- bin\x86\Debug\
- DEBUG;TRACE
- full
- x86
- prompt
- MinimumRecommendedRules.ruleset
-
-
- bin\x86\Release\
- TRACE
- true
- pdbonly
- x86
- prompt
- MinimumRecommendedRules.ruleset
-
-
- bin\x86\Any CPU\
- TRACE
- true
- pdbonly
- x86
- prompt
- MinimumRecommendedRules.ruleset
-
-
- true
- bin\x64\Debug\
- DEBUG;TRACE
- full
- x64
- prompt
- MinimumRecommendedRules.ruleset
-
-
- bin\x64\Release\
- TRACE
- true
- pdbonly
- x64
- prompt
- MinimumRecommendedRules.ruleset
-
-
- bin\x64\Any CPU\
- TRACE
- true
- pdbonly
- x64
- prompt
- MinimumRecommendedRules.ruleset
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {2ecb99e4-72d0-4c23-99ba-93d511d3967d}
- MQTTnet.Core
-
-
-
-
-
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs b/Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs
deleted file mode 100644
index 15da033d..00000000
--- a/Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using MQTTnet.Core.Client;
-using MQTTnet.Implementations;
-
-namespace MQTTnet
-{
- public class MqttClientFactory : IMqttClientFactory
- {
- public IMqttClient CreateMqttClient()
- {
- return new MqttClient(new MqttCommunicationAdapterFactory());
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs b/Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs
deleted file mode 100644
index 70e14f3c..00000000
--- a/Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Server;
-using MQTTnet.Implementations;
-
-namespace MQTTnet
-{
- public class MqttServerFactory : IMqttServerFactory
- {
- public IMqttServer CreateMqttServer(MqttServerOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- return new MqttServer(options, new List { new MqttServerAdapter() });
- }
- }
-}
diff --git a/Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs b/Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs
deleted file mode 100644
index 4307dbcf..00000000
--- a/Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("MQTTnet")]
-[assembly: AssemblyDescription("MQTTnet for .NET Framework")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Christian Kratky")]
-[assembly: AssemblyProduct("MQTTnet")]
-[assembly: AssemblyCopyright("Copyright © Christian Kratky 2016-2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-[assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")]
-[assembly: AssemblyVersion("2.4.0.0")]
-[assembly: AssemblyFileVersion("2.4.0.0")]
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs
deleted file mode 100644
index 799ccddd..00000000
--- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttCommunicationAdapterFactory.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Client;
-using MQTTnet.Core.Serializer;
-
-namespace MQTTnet.Implementations
-{
- public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
- {
- public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- if (options is MqttClientTcpOptions tcpOptions)
- {
- return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
- }
-
- if (options is MqttClientWebSocketOptions webSocketOptions)
- {
- return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
- }
-
- throw new NotSupportedException();
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs
index 56650dde..2d3db159 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs
@@ -7,28 +7,33 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Serializer;
using MQTTnet.Core.Server;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Core.Client;
namespace MQTTnet.Implementations
{
public class MqttServerAdapter : IMqttServerAdapter, IDisposable
{
+ private readonly ILogger _logger;
+ private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;
private CancellationTokenSource _cancellationTokenSource;
private Socket _defaultEndpointSocket;
private Socket _tlsEndpointSocket;
private X509Certificate2 _tlsCertificate;
- private bool _isRunning;
+ public MqttServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
+ }
- public event Action ClientAccepted;
+ public event EventHandler ClientAccepted;
public Task StartAsync(MqttServerOptions options)
{
- if (_isRunning) throw new InvalidOperationException("Server is already started.");
-
- _isRunning = true;
+ if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started.");
_cancellationTokenSource = new CancellationTokenSource();
@@ -49,6 +54,10 @@ namespace MQTTnet.Implementations
}
_tlsCertificate = new X509Certificate2(options.TlsEndpointOptions.Certificate);
+ if (!_tlsCertificate.HasPrivateKey)
+ {
+ throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
+ }
_tlsEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
_tlsEndpointSocket.Bind(new IPEndPoint(IPAddress.Any, options.GetTlsEndpointPort()));
@@ -62,8 +71,6 @@ namespace MQTTnet.Implementations
public Task StopAsync()
{
- _isRunning = false;
-
_cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
@@ -71,6 +78,8 @@ namespace MQTTnet.Implementations
_defaultEndpointSocket?.Dispose();
_defaultEndpointSocket = null;
+ _tlsCertificate = null;
+
_tlsEndpointSocket?.Dispose();
_tlsEndpointSocket = null;
@@ -88,13 +97,18 @@ namespace MQTTnet.Implementations
{
try
{
+ //todo: else branch can be used with min dependency NET46
+#if NET451
+ var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
+#else
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
- var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
- ClientAccepted?.Invoke(clientAdapter);
+#endif
+ var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, null));
+ ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
+ _logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint.");
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
@@ -108,17 +122,21 @@ namespace MQTTnet.Implementations
{
try
{
+#if NET451
+ var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false);
+#else
var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false);
+#endif
var sslStream = new SslStream(new NetworkStream(clientSocket));
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
- var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer());
- ClientAccepted?.Invoke(clientAdapter);
+ var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream));
+ ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
+ _logger.LogError(new EventId(), exception, "Error while accepting connection at TLS endpoint.");
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
index e60f641d..7c051bfd 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
@@ -15,6 +15,13 @@ namespace MQTTnet.Implementations
{
private readonly MqttClientTcpOptions _options;
+//todo: this can be used with min dependency NetStandard1.6
+#if NET45
+ // 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.
+#endif
+
private Socket _socket;
private SslStream _sslStream;
@@ -34,13 +41,14 @@ namespace MQTTnet.Implementations
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream;
- ReceiveStream = (Stream)sslStream ?? new NetworkStream(socket);
+
+ CreateStreams(socket, sslStream);
}
- public Stream SendStream => ReceiveStream;
+ public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
- public Stream RawReceiveStream => ReceiveStream;
-
+ public Stream RawReceiveStream { get; private set; }
+
public static Func CustomCertificateValidationCallback { get; set; }
public async Task ConnectAsync()
@@ -50,18 +58,20 @@ namespace MQTTnet.Implementations
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}
+//todo: else brach can be used with min dependency NET46
+#if NET451
+ 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);
+#endif
if (_options.TlsOptions.UseTls)
{
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
- ReceiveStream = _sslStream;
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
}
- else
- {
- ReceiveStream = new NetworkStream(_socket);
- }
+
+ CreateStreams(_socket, _sslStream);
}
public Task DisconnectAsync()
@@ -110,7 +120,7 @@ namespace MQTTnet.Implementations
return _options.TlsOptions.AllowUntrustedCertificates;
}
- private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
+ private static X509CertificateCollection LoadCertificates(IMqttClientOptions options)
{
var certificates = new X509CertificateCollection();
if (options.TlsOptions.Certificates == null)
@@ -125,5 +135,24 @@ namespace MQTTnet.Implementations
return certificates;
}
+
+ private void CreateStreams(Socket socket, Stream sslStream)
+ {
+ 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);
+#else
+ SendStream = RawReceiveStream;
+ ReceiveStream = RawReceiveStream;
+#endif
+ }
+
}
}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
index 0433b80f..081c74a2 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/MqttWebSocketChannel.cs
@@ -3,6 +3,7 @@ using MQTTnet.Core.Client;
using System;
using System.IO;
using System.Net.WebSockets;
+using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -32,6 +33,37 @@ namespace MQTTnet.Implementations
_webSocket = new ClientWebSocket();
_webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod;
+
+ if (_options.RequestHeaders != null)
+ {
+ foreach (var requestHeader in _options.RequestHeaders)
+ {
+ _webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
+ }
+ }
+
+ if (_options.SubProtocols != null)
+ {
+ foreach (var subProtocol in _options.SubProtocols)
+ {
+ _webSocket.Options.AddSubProtocol(subProtocol);
+ }
+ }
+
+ if (_options.CookieContainer != null)
+ {
+ _webSocket.Options.Cookies = _options.CookieContainer;
+ }
+
+ if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
+ {
+ _webSocket.Options.ClientCertificates = new X509CertificateCollection();
+ foreach (var certificate in _options.TlsOptions.Certificates)
+ {
+ _webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
+ }
+ }
+
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
RawReceiveStream = new WebSocketStream(_webSocket);
}
diff --git a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
index 6efe7506..c27e4801 100644
--- a/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
+++ b/Frameworks/MQTTnet.NetStandard/Implementations/WebSocketStream.cs
@@ -3,16 +3,17 @@ using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Core.Exceptions;
namespace MQTTnet.Implementations
{
public class WebSocketStream : Stream
{
- private readonly ClientWebSocket _webSocket;
+ private readonly WebSocket _webSocket;
- public WebSocketStream(ClientWebSocket webSocket)
+ public WebSocketStream(WebSocket webSocket)
{
- _webSocket = webSocket;
+ _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
}
public override bool CanRead => true;
@@ -41,6 +42,7 @@ namespace MQTTnet.Implementations
{
var response = await _webSocket.ReceiveAsync(new ArraySegment(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
currentOffset += response.Count;
+ count -= response.Count;
if (response.MessageType == WebSocketMessageType.Close)
{
@@ -48,6 +50,11 @@ namespace MQTTnet.Implementations
}
}
+ if (_webSocket.State == WebSocketState.Closed)
+ {
+ throw new MqttCommunicationException( "connection closed" );
+ }
+
return currentOffset - offset;
}
diff --git a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
index 6e009895..f8dbcc9f 100644
--- a/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
+++ b/Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
@@ -1,11 +1,11 @@
- netstandard1.3
+ netstandard1.3;net451
MQTTnet
MQTTnet
- 2.4.0.0
- 2.4.0.0
+ 2.5.0.0
+ 2.5.0.0
0.0.0.0
@@ -19,10 +19,15 @@
-
+
+
+
+
+
+
diff --git a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs
deleted file mode 100644
index 15da033d..00000000
--- a/Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using MQTTnet.Core.Client;
-using MQTTnet.Implementations;
-
-namespace MQTTnet
-{
- public class MqttClientFactory : IMqttClientFactory
- {
- public IMqttClient CreateMqttClient()
- {
- return new MqttClient(new MqttCommunicationAdapterFactory());
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/MqttFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs
new file mode 100644
index 00000000..2c18ed96
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/MqttFactory.cs
@@ -0,0 +1,109 @@
+using System;
+using MQTTnet.Core.Adapter;
+using MQTTnet.Core.Client;
+using MQTTnet.Core.Serializer;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.DependencyInjection;
+using MQTTnet.Implementations;
+using MQTTnet.Core.ManagedClient;
+using MQTTnet.Core.Server;
+using System.Linq;
+using MQTTnet.Core.Channel;
+
+namespace MQTTnet
+{
+ public class MqttFactory : IMqttCommunicationAdapterFactory, IMqttClientSesssionFactory, 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 IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options)
+ {
+ var logger = _serviceProvider.GetRequiredService>();
+ return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options), 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(IMqttClientOptions 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 sessionId, MqttClientSessionsManager mqttClientSessionsManager)
+ {
+ return new MqttClientSession(sessionId, mqttClientSessionsManager, _serviceProvider.GetRequiredService>(), _serviceProvider.GetRequiredService>());
+ }
+
+ public IMqttClient CreateMqttClient()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+
+ public ManagedMqttClient CreateManagedMqttClient()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+
+ public IMqttServer CreateMqttServer()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs b/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs
deleted file mode 100644
index 70e14f3c..00000000
--- a/Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Server;
-using MQTTnet.Implementations;
-
-namespace MQTTnet
-{
- public class MqttServerFactory : IMqttServerFactory
- {
- public IMqttServer CreateMqttServer(MqttServerOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- return new MqttServer(options, new List { new MqttServerAdapter() });
- }
- }
-}
diff --git a/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..019f39ba
--- /dev/null
+++ b/Frameworks/MQTTnet.NetStandard/ServiceCollectionExtensions.cs
@@ -0,0 +1,68 @@
+using Microsoft.Extensions.DependencyInjection;
+using MQTTnet.Core.Adapter;
+using MQTTnet.Core.Client;
+using MQTTnet.Core.ManagedClient;
+using MQTTnet.Core.Serializer;
+using MQTTnet.Core.Server;
+using MQTTnet.Implementations;
+using System;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Core.Diagnostics;
+
+namespace MQTTnet
+{
+ public static class ServiceCollectionExtensions
+ {
+ public static IServiceCollection AddMqttServer(this IServiceCollection services)
+ {
+ services.AddMqttServerServices();
+
+ services.AddSingleton(s => s.GetService());
+ services.AddSingleton();
+
+ return services;
+ }
+
+ public static IServiceCollection AddMqttServerServices(this IServiceCollection services)
+ {
+ services.AddOptions();
+ services.AddSingleton();
+ services.AddSingleton(s => s.GetService());
+ services.AddSingleton(s => s.GetService());
+
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient();
+ services.AddTransient();
+ return services;
+ }
+
+ public static IServiceCollection AddMqttServer(this IServiceCollection services, Action configureOptions)
+ {
+ return services
+ .AddMqttServer()
+ .Configure(configureOptions);
+ }
+
+ public static IServiceCollection AddMqttClient(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton(s => s.GetService());
+
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ return services;
+ }
+
+ public static ILoggerFactory AddMqttTrace(this ILoggerFactory factory)
+ {
+ factory.AddProvider(new MqttNetTrace());
+ return factory;
+ }
+ }
+}
diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs
deleted file mode 100644
index 799ccddd..00000000
--- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttCommunicationAdapterFactory.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Client;
-using MQTTnet.Core.Serializer;
-
-namespace MQTTnet.Implementations
-{
- public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
- {
- public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- if (options is MqttClientTcpOptions tcpOptions)
- {
- return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
- }
-
- if (options is MqttClientWebSocketOptions webSocketOptions)
- {
- return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
- }
-
- throw new NotSupportedException();
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs
index 1a742cba..373cdac4 100644
--- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs
+++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs
@@ -1,29 +1,32 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
-using MQTTnet.Core.Serializer;
using MQTTnet.Core.Server;
using Windows.Networking.Sockets;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Implementations
{
public class MqttServerAdapter : IMqttServerAdapter, IDisposable
{
+ private readonly ILogger _logger;
+ private readonly IMqttCommunicationAdapterFactory _mqttCommunicationAdapterFactory;
private StreamSocketListener _defaultEndpointSocket;
- private bool _isRunning;
+ public MqttServerAdapter(ILogger logger, IMqttCommunicationAdapterFactory mqttCommunicationAdapterFactory)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _mqttCommunicationAdapterFactory = mqttCommunicationAdapterFactory ?? throw new ArgumentNullException(nameof(mqttCommunicationAdapterFactory));
+ }
- public event Action ClientAccepted;
+ public event EventHandler ClientAccepted;
public async Task StartAsync(MqttServerOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
- if (_isRunning) throw new InvalidOperationException("Server is already started.");
-
- _isRunning = true;
-
+ if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started.");
+
if (options.DefaultEndpointOptions.IsEnabled)
{
_defaultEndpointSocket = new StreamSocketListener();
@@ -39,8 +42,6 @@ namespace MQTTnet.Implementations
public Task StopAsync()
{
- _isRunning = false;
-
_defaultEndpointSocket?.Dispose();
_defaultEndpointSocket = null;
@@ -56,12 +57,12 @@ namespace MQTTnet.Implementations
{
try
{
- var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer());
- ClientAccepted?.Invoke(clientAdapter);
+ var clientAdapter = _mqttCommunicationAdapterFactory.CreateServerMqttCommunicationAdapter(new MqttTcpChannel(args.Socket));
+ ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
+ _logger.LogError(new EventId(), exception, "Error while accepting connection at default endpoint.");
}
}
}
diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs
index 03206f1f..1a96f1e4 100644
--- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs
+++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs
@@ -89,7 +89,7 @@ namespace MQTTnet.Implementations
RawReceiveStream = ReceiveStream;
}
- private static Certificate LoadCertificate(MqttClientOptions options)
+ private static Certificate LoadCertificate(BaseMqttClientOptions options)
{
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
{
diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs
index 471cdbf8..48433165 100644
--- a/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs
+++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/MqttWebSocketChannel.cs
@@ -3,6 +3,7 @@ using MQTTnet.Core.Client;
using System;
using System.IO;
using System.Net.WebSockets;
+using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -32,6 +33,37 @@ namespace MQTTnet.Implementations
_webSocket = new ClientWebSocket();
_webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod;
+
+ if (_options.RequestHeaders != null)
+ {
+ foreach (var requestHeader in _options.RequestHeaders)
+ {
+ _webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
+ }
+ }
+
+ if (_options.SubProtocols != null)
+ {
+ foreach (var subProtocol in _options.SubProtocols)
+ {
+ _webSocket.Options.AddSubProtocol(subProtocol);
+ }
+ }
+
+ if (_options.CookieContainer != null)
+ {
+ _webSocket.Options.Cookies = _options.CookieContainer;
+ }
+
+ if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
+ {
+ _webSocket.Options.ClientCertificates = new X509CertificateCollection();
+ foreach (var certificate in _options.TlsOptions.Certificates)
+ {
+ _webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
+ }
+ }
+
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
RawReceiveStream = new WebSocketStream(_webSocket);
diff --git a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs
index 69225b9f..718b5eec 100644
--- a/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs
+++ b/Frameworks/MQTTnet.UniversalWindows/Implementations/WebSocketStream.cs
@@ -3,16 +3,17 @@ using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Core.Exceptions;
namespace MQTTnet.Implementations
{
public class WebSocketStream : Stream
{
- private readonly ClientWebSocket _webSocket;
+ private readonly WebSocket _webSocket;
- public WebSocketStream(ClientWebSocket webSocket)
+ public WebSocketStream(WebSocket webSocket)
{
- _webSocket = webSocket;
+ _webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
}
public override void Flush()
@@ -27,13 +28,19 @@ namespace MQTTnet.Implementations
{
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;
}
diff --git a/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj b/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj
index 71081e0a..d8051f81 100644
--- a/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj
+++ b/Frameworks/MQTTnet.UniversalWindows/MQTTnet.UniversalWindows.csproj
@@ -110,14 +110,13 @@
bin\x64\Release\MQTTnet.XML
-
-
+
-
+
@@ -127,6 +126,12 @@
+
+ 1.1.1
+
+
+ 1.1.2
+
5.4.0
diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs
deleted file mode 100644
index 15da033d..00000000
--- a/Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using MQTTnet.Core.Client;
-using MQTTnet.Implementations;
-
-namespace MQTTnet
-{
- public class MqttClientFactory : IMqttClientFactory
- {
- public IMqttClient CreateMqttClient()
- {
- return new MqttClient(new MqttCommunicationAdapterFactory());
- }
- }
-}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs
new file mode 100644
index 00000000..9761150e
--- /dev/null
+++ b/Frameworks/MQTTnet.UniversalWindows/MqttFactory.cs
@@ -0,0 +1,108 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Core.Adapter;
+using MQTTnet.Core.Channel;
+using MQTTnet.Core.Client;
+using MQTTnet.Core.ManagedClient;
+using MQTTnet.Core.Serializer;
+using MQTTnet.Core.Server;
+using MQTTnet.Implementations;
+
+namespace MQTTnet
+{
+ public class MqttFactory : IMqttCommunicationAdapterFactory, IMqttClientSesssionFactory, 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 IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options)
+ {
+ var logger = _serviceProvider.GetRequiredService>();
+ return new MqttChannelCommunicationAdapter(CreateMqttCommunicationChannel(options), 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(IMqttClientOptions 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 sessionId, MqttClientSessionsManager mqttClientSessionsManager)
+ {
+ return new MqttClientSession(sessionId, mqttClientSessionsManager, _serviceProvider.GetRequiredService>(), _serviceProvider.GetRequiredService>());
+ }
+
+ public IMqttClient CreateMqttClient()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+
+ public ManagedMqttClient CreateManagedMqttClient()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+
+ public IMqttServer CreateMqttServer()
+ {
+ return _serviceProvider.GetRequiredService();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs b/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs
deleted file mode 100644
index 70e14f3c..00000000
--- a/Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Server;
-using MQTTnet.Implementations;
-
-namespace MQTTnet
-{
- public class MqttServerFactory : IMqttServerFactory
- {
- public IMqttServer CreateMqttServer(MqttServerOptions options)
- {
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- return new MqttServer(options, new List { new MqttServerAdapter() });
- }
- }
-}
diff --git a/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs b/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs
index 54988c72..57632880 100644
--- a/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs
+++ b/Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs
@@ -10,5 +10,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
-[assembly: AssemblyVersion("2.4.0.0")]
-[assembly: AssemblyFileVersion("2.4.0.0")]
\ No newline at end of file
+[assembly: AssemblyVersion("2.5.0.0")]
+[assembly: AssemblyFileVersion("2.5.0.0")]
\ No newline at end of file
diff --git a/Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs b/Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..f8f647a9
--- /dev/null
+++ b/Frameworks/MQTTnet.UniversalWindows/ServiceCollectionExtensions.cs
@@ -0,0 +1,62 @@
+using Microsoft.Extensions.DependencyInjection;
+using MQTTnet.Core.Adapter;
+using MQTTnet.Core.Client;
+using MQTTnet.Core.ManagedClient;
+using MQTTnet.Core.Serializer;
+using MQTTnet.Core.Server;
+using MQTTnet.Implementations;
+using System;
+using Microsoft.Extensions.Logging;
+using MQTTnet.Core.Diagnostics;
+
+namespace MQTTnet
+{
+ public static class ServiceCollectionExtensions
+ {
+ public static IServiceCollection AddMqttServer(this IServiceCollection services)
+ {
+ services.AddOptions();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddTransient();
+ services.AddTransient();
+
+ services.AddTransient();
+ services.AddTransient();
+ return services;
+ }
+
+ public static IServiceCollection AddMqttServer(this IServiceCollection services, Action configureOptions)
+ {
+ return services
+ .AddMqttServer()
+ .Configure(configureOptions);
+ }
+
+ public static IServiceCollection AddMqttClient(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ return services;
+ }
+
+ public static ILoggerFactory AddMqttTrace(this ILoggerFactory factory)
+ {
+ factory.AddProvider(new MqttNetTrace());
+ return factory;
+ }
+ }
+}
diff --git a/LICENSE b/LICENSE
index 14484d8f..7d84e2b1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 Christian
+Copyright (c) 2017 Christian Kratky
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs b/MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs
new file mode 100644
index 00000000..ffcfd2c1
--- /dev/null
+++ b/MQTTnet.Core/Adapter/IMqttCommunicationAdapterFactory.cs
@@ -0,0 +1,12 @@
+using MQTTnet.Core.Client;
+using MQTTnet.Core.Channel;
+
+namespace MQTTnet.Core.Adapter
+{
+ public interface IMqttCommunicationAdapterFactory
+ {
+ IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options);
+
+ IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel);
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Adapter/IMqttServerAdapter.cs b/MQTTnet.Core/Adapter/IMqttServerAdapter.cs
index b1546efd..9209185e 100644
--- a/MQTTnet.Core/Adapter/IMqttServerAdapter.cs
+++ b/MQTTnet.Core/Adapter/IMqttServerAdapter.cs
@@ -6,7 +6,7 @@ namespace MQTTnet.Core.Adapter
{
public interface IMqttServerAdapter
{
- event Action ClientAccepted;
+ event EventHandler ClientAccepted;
Task StartAsync(MqttServerOptions options);
Task StopAsync();
diff --git a/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs b/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs
index 7fae37b0..d47d4cee 100644
--- a/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs
+++ b/MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs
@@ -4,21 +4,23 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Core.Adapter
{
public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter
{
+ private readonly ILogger _logger;
private readonly IMqttCommunicationChannel _channel;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
- public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer)
+ public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer, ILogger logger)
{
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
}
@@ -94,7 +96,7 @@ namespace MQTTnet.Core.Adapter
continue;
}
- MqttNetTrace.Information(nameof(MqttChannelCommunicationAdapter), "TX >>> {0} [Timeout={1}]", packet, timeout);
+ _logger.LogInformation("TX >>> {0} [Timeout={1}]", packet, timeout);
var writeBuffer = PacketSerializer.Serialize(packet);
await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false);
@@ -160,7 +162,7 @@ namespace MQTTnet.Core.Adapter
throw new MqttProtocolViolationException("Received malformed packet.");
}
- MqttNetTrace.Information(nameof(MqttChannelCommunicationAdapter), "RX <<< {0}", packet);
+ _logger.LogInformation("RX <<< {0}", packet);
return packet;
}
catch (TaskCanceledException)
diff --git a/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs b/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
new file mode 100644
index 00000000..6caca0e9
--- /dev/null
+++ b/MQTTnet.Core/Adapter/MqttServerAdapterClientAcceptedEventArgs.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Core.Adapter
+{
+ public class MqttServerAdapterClientAcceptedEventArgs : EventArgs
+ {
+ public MqttServerAdapterClientAcceptedEventArgs(IMqttCommunicationAdapter client)
+ {
+ Client = client ?? throw new ArgumentNullException(nameof(client));
+ }
+
+ public IMqttCommunicationAdapter Client { get; }
+
+ public Task SessionTask { get; set; }
+ }
+}
diff --git a/MQTTnet.Core/Client/MqttClientOptions.cs b/MQTTnet.Core/Client/BaseMqttClientOptions.cs
similarity index 89%
rename from MQTTnet.Core/Client/MqttClientOptions.cs
rename to MQTTnet.Core/Client/BaseMqttClientOptions.cs
index f2e28fbb..ca3f82e2 100644
--- a/MQTTnet.Core/Client/MqttClientOptions.cs
+++ b/MQTTnet.Core/Client/BaseMqttClientOptions.cs
@@ -3,7 +3,7 @@ using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Client
{
- public abstract class MqttClientOptions
+ public abstract class BaseMqttClientOptions : IMqttClientOptions
{
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
@@ -13,7 +13,7 @@ namespace MQTTnet.Core.Client
public string Password { get; set; }
- public string ClientId { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty);
+ public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
public bool CleanSession { get; set; } = true;
diff --git a/MQTTnet.Core/Client/IApplicationMessageReceiver.cs b/MQTTnet.Core/Client/IApplicationMessageReceiver.cs
new file mode 100644
index 00000000..2d53cb8a
--- /dev/null
+++ b/MQTTnet.Core/Client/IApplicationMessageReceiver.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace MQTTnet.Core.Client
+{
+ public interface IApplicationMessageReceiver
+ {
+ event EventHandler ApplicationMessageReceived;
+ }
+}
diff --git a/MQTTnet.Core/Client/IMqttClient.cs b/MQTTnet.Core/Client/IMqttClient.cs
index 7622867b..9caefbda 100644
--- a/MQTTnet.Core/Client/IMqttClient.cs
+++ b/MQTTnet.Core/Client/IMqttClient.cs
@@ -5,15 +5,14 @@ using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Client
{
- public interface IMqttClient
+ public interface IMqttClient : IApplicationMessageReceiver
{
bool IsConnected { get; }
- event EventHandler ApplicationMessageReceived;
- event EventHandler Connected;
- event EventHandler Disconnected;
+ event EventHandler Connected;
+ event EventHandler Disconnected;
- Task ConnectAsync(MqttClientOptions options);
+ Task ConnectAsync(IMqttClientOptions options);
Task DisconnectAsync();
Task> SubscribeAsync(IEnumerable topicFilters);
diff --git a/MQTTnet.Core/Client/IMqttClientFactory.cs b/MQTTnet.Core/Client/IMqttClientFactory.cs
index 033eb993..7547906e 100644
--- a/MQTTnet.Core/Client/IMqttClientFactory.cs
+++ b/MQTTnet.Core/Client/IMqttClientFactory.cs
@@ -1,7 +1,11 @@
-namespace MQTTnet.Core.Client
+using MQTTnet.Core.ManagedClient;
+
+namespace MQTTnet.Core.Client
{
public interface IMqttClientFactory
{
IMqttClient CreateMqttClient();
+
+ ManagedMqttClient CreateManagedMqttClient();
}
}
\ No newline at end of file
diff --git a/MQTTnet.Core/Client/IMqttClientOptions.cs b/MQTTnet.Core/Client/IMqttClientOptions.cs
new file mode 100644
index 00000000..7e675ff8
--- /dev/null
+++ b/MQTTnet.Core/Client/IMqttClientOptions.cs
@@ -0,0 +1,18 @@
+using System;
+using MQTTnet.Core.Serializer;
+
+namespace MQTTnet.Core.Client
+{
+ public interface IMqttClientOptions
+ {
+ bool CleanSession { get; }
+ string ClientId { get; }
+ TimeSpan DefaultCommunicationTimeout { get; }
+ TimeSpan KeepAlivePeriod { get; }
+ string Password { get; }
+ MqttProtocolVersion ProtocolVersion { get; }
+ MqttClientTlsOptions TlsOptions { get; }
+ string UserName { get; }
+ MqttApplicationMessage WillMessage { get; }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs b/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs
deleted file mode 100644
index 092ea040..00000000
--- a/MQTTnet.Core/Client/IMqttCommunicationAdapterFactory.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using MQTTnet.Core.Adapter;
-
-namespace MQTTnet.Core.Client
-{
- public interface IMqttCommunicationAdapterFactory
- {
- IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options);
- }
-}
\ No newline at end of file
diff --git a/MQTTnet.Core/Client/MqttClient.cs b/MQTTnet.Core/Client/MqttClient.cs
index 80b850a8..67ffb126 100644
--- a/MQTTnet.Core/Client/MqttClient.cs
+++ b/MQTTnet.Core/Client/MqttClient.cs
@@ -4,38 +4,42 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Core.Client
{
public class MqttClient : IMqttClient
{
private readonly HashSet _unacknowledgedPublishPackets = new HashSet();
- private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
- private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory;
+ private readonly MqttPacketDispatcher _packetDispatcher;
+ private readonly IMqttCommunicationAdapterFactory _communicationAdapterFactory;
+ private readonly ILogger _logger;
- private MqttClientOptions _options;
+ private IMqttClientOptions _options;
private bool _isReceivingPackets;
private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
+ private IDisposable _scopeHandle;
- public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory)
+ public MqttClient(IMqttCommunicationAdapterFactory communicationAdapterFactory, ILogger logger, MqttPacketDispatcher packetDispatcher)
{
- _communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory));
+ _communicationAdapterFactory = communicationAdapterFactory ?? throw new ArgumentNullException(nameof(communicationAdapterFactory));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _packetDispatcher = packetDispatcher ?? throw new ArgumentNullException(nameof(packetDispatcher));
}
- public event EventHandler Connected;
- public event EventHandler Disconnected;
+ public event EventHandler Connected;
+ public event EventHandler Disconnected;
public event EventHandler ApplicationMessageReceived;
- public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested;
+ public bool IsConnected { get; private set; }
- public async Task ConnectAsync(MqttClientOptions options)
+ public async Task ConnectAsync(IMqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
@@ -48,23 +52,26 @@ namespace MQTTnet.Core.Client
_latestPacketIdentifier = 0;
_packetDispatcher.Reset();
- _adapter = _communicationChannelFactory.CreateMqttCommunicationAdapter(options);
-
- MqttNetTrace.Verbose(nameof(MqttClient), "Trying to connect with server.");
+ _adapter = _communicationAdapterFactory.CreateClientMqttCommunicationAdapter(options);
+
+ _scopeHandle = _logger.BeginScope(options.ClientId);
+ _logger.LogTrace("Trying to connect with server.");
await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
- MqttNetTrace.Verbose(nameof(MqttClient), "Connection with server established.");
+ _logger.LogTrace("Connection with server established.");
await SetupIncomingPacketProcessingAsync();
- await AuthenticateAsync(options.WillMessage);
+ var connectResponse = await AuthenticateAsync(options.WillMessage);
- MqttNetTrace.Verbose(nameof(MqttClient), "MQTT connection with server established.");
+ _logger.LogTrace("MQTT connection with server established.");
if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
StartSendKeepAliveMessages(_cancellationTokenSource.Token);
}
- Connected?.Invoke(this, EventArgs.Empty);
+ IsConnected = true;
+ Connected?.Invoke(this, new MqttClientConnectedEventArgs(connectResponse.IsSessionPresent));
+ return new MqttClientConnectResult(connectResponse.IsSessionPresent);
}
catch (Exception)
{
@@ -87,6 +94,7 @@ namespace MQTTnet.Core.Client
finally
{
await DisconnectInternalAsync().ConfigureAwait(false);
+ _scopeHandle.Dispose();
}
}
@@ -173,7 +181,7 @@ namespace MQTTnet.Core.Client
}
}
- private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
+ private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
{
var connectPacket = new MqttConnectPacket
{
@@ -190,6 +198,8 @@ namespace MQTTnet.Core.Client
{
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}
+
+ return response;
}
private async Task SetupIncomingPacketProcessingAsync()
@@ -222,6 +232,9 @@ namespace MQTTnet.Core.Client
private async Task DisconnectInternalAsync()
{
+ var clientWasConnected = IsConnected;
+ IsConnected = false;
+
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
@@ -235,15 +248,15 @@ namespace MQTTnet.Core.Client
try
{
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
- MqttNetTrace.Information(nameof(MqttClient), "Disconnected from adapter.");
+ _logger.LogInformation("Disconnected from adapter.");
}
catch (Exception exception)
{
- MqttNetTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter.");
+ _logger.LogWarning(new EventId(), exception, "Error while disconnecting from adapter.");
}
finally
{
- Disconnected?.Invoke(this, EventArgs.Empty);
+ Disconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(clientWasConnected));
}
}
@@ -251,7 +264,7 @@ namespace MQTTnet.Core.Client
{
try
{
- MqttNetTrace.Information(nameof(MqttClient), "Received <<< {0}", packet);
+ _logger.LogInformation("Received <<< {0}", packet);
if (packet is MqttPingReqPacket)
{
@@ -281,7 +294,7 @@ namespace MQTTnet.Core.Client
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet.");
+ _logger.LogError(new EventId(), exception, "Unhandled exception while processing received packet.");
}
}
@@ -294,7 +307,7 @@ namespace MQTTnet.Core.Client
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message.");
+ _logger.LogError(new EventId(), exception, "Unhandled exception while handling application message.");
}
}
@@ -358,7 +371,7 @@ namespace MQTTnet.Core.Client
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{
- MqttNetTrace.Information(nameof(MqttClient), "Start sending keep alive packets.");
+ _logger.LogInformation("Start sending keep alive packets.");
try
{
@@ -383,23 +396,23 @@ namespace MQTTnet.Core.Client
return;
}
- MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets.");
+ _logger.LogWarning(new EventId(), exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (Exception exception)
{
- MqttNetTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets.");
+ _logger.LogWarning(new EventId(), exception, "Unhandled exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
- MqttNetTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets.");
+ _logger.LogInformation("Stopped sending keep alive packets.");
}
}
private async Task ReceivePackets(CancellationToken cancellationToken)
{
- MqttNetTrace.Information(nameof(MqttClient), "Start receiving packets.");
+ _logger.LogInformation("Start receiving packets.");
try
{
@@ -426,17 +439,17 @@ namespace MQTTnet.Core.Client
return;
}
- MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets.");
+ _logger.LogWarning(new EventId(), exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets.");
+ _logger.LogError(new EventId(), exception, "Unhandled exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
- MqttNetTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
+ _logger.LogInformation(nameof(MqttClient), "Stopped receiving packets.");
}
}
diff --git a/MQTTnet.Core/Client/MqttClientConnectResult.cs b/MQTTnet.Core/Client/MqttClientConnectResult.cs
new file mode 100644
index 00000000..57db57df
--- /dev/null
+++ b/MQTTnet.Core/Client/MqttClientConnectResult.cs
@@ -0,0 +1,12 @@
+namespace MQTTnet.Core.Client
+{
+ public class MqttClientConnectResult
+ {
+ public MqttClientConnectResult(bool isSessionPresent)
+ {
+ IsSessionPresent = isSessionPresent;
+ }
+
+ public bool IsSessionPresent { get; }
+ }
+}
diff --git a/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs b/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs
new file mode 100644
index 00000000..107a2c95
--- /dev/null
+++ b/MQTTnet.Core/Client/MqttClientConnectedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace MQTTnet.Core.Client
+{
+ public class MqttClientConnectedEventArgs : EventArgs
+ {
+ public MqttClientConnectedEventArgs(bool isSessionPresent)
+ {
+ IsSessionPresent = isSessionPresent;
+ }
+
+ public bool IsSessionPresent { get; }
+ }
+}
diff --git a/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs b/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs
new file mode 100644
index 00000000..1e9609ef
--- /dev/null
+++ b/MQTTnet.Core/Client/MqttClientDisconnectedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace MQTTnet.Core.Client
+{
+ public class MqttClientDisconnectedEventArgs : EventArgs
+ {
+ public MqttClientDisconnectedEventArgs(bool clientWasConnected)
+ {
+ ClientWasConnected = clientWasConnected;
+ }
+
+ public bool ClientWasConnected { get; }
+ }
+}
diff --git a/MQTTnet.Core/Client/MqttClientTcpOptions.cs b/MQTTnet.Core/Client/MqttClientTcpOptions.cs
index beaa506a..51d2b973 100644
--- a/MQTTnet.Core/Client/MqttClientTcpOptions.cs
+++ b/MQTTnet.Core/Client/MqttClientTcpOptions.cs
@@ -1,6 +1,6 @@
namespace MQTTnet.Core.Client
{
- public class MqttClientTcpOptions : MqttClientOptions
+ public class MqttClientTcpOptions : BaseMqttClientOptions
{
public string Server { get; set; }
diff --git a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs
index 4b905242..522fb9bc 100644
--- a/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs
+++ b/MQTTnet.Core/Client/MqttClientWebSocketOptions.cs
@@ -1,7 +1,16 @@
-namespace MQTTnet.Core.Client
+using System.Collections.Generic;
+using System.Net;
+
+namespace MQTTnet.Core.Client
{
- public class MqttClientWebSocketOptions : MqttClientOptions
+ public class MqttClientWebSocketOptions : BaseMqttClientOptions
{
public string Uri { get; set; }
+
+ public IDictionary RequestHeaders { get; set; }
+
+ public ICollection SubProtocols { get; set; }
+
+ public CookieContainer CookieContainer { get; set; }
}
}
diff --git a/MQTTnet.Core/Client/MqttPacketDispatcher.cs b/MQTTnet.Core/Client/MqttPacketDispatcher.cs
index 484012e4..a999d697 100644
--- a/MQTTnet.Core/Client/MqttPacketDispatcher.cs
+++ b/MQTTnet.Core/Client/MqttPacketDispatcher.cs
@@ -1,10 +1,10 @@
using System;
+using System.Collections.Concurrent;
using System.Threading.Tasks;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
-using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Core.Client
{
@@ -12,6 +12,12 @@ namespace MQTTnet.Core.Client
{
private readonly ConcurrentDictionary> _packetByResponseType = new ConcurrentDictionary>();
private readonly ConcurrentDictionary>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary>>();
+ private readonly ILogger _logger;
+
+ public MqttPacketDispatcher(ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
public async Task WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout)
{
@@ -24,7 +30,7 @@ namespace MQTTnet.Core.Client
}
catch (MqttCommunicationTimedOutException)
{
- MqttNetTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet of type '{0}'.", responseType.Name);
+ _logger.LogWarning("Timeout while waiting for packet of type '{0}'.", responseType.Name);
throw;
}
finally
diff --git a/MQTTnet.Core/Diagnostics/MqttNetLogger.cs b/MQTTnet.Core/Diagnostics/MqttNetLogger.cs
new file mode 100644
index 00000000..54ab6d44
--- /dev/null
+++ b/MQTTnet.Core/Diagnostics/MqttNetLogger.cs
@@ -0,0 +1,49 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace MQTTnet.Core.Diagnostics
+{
+ public class MqttNetLogger : ILogger
+ {
+ private readonly string _categoryName;
+ private readonly MqttNetTrace _mqttNetTrace;
+
+ public MqttNetLogger(string categoryName, MqttNetTrace mqttNetTrace)
+ {
+ _categoryName = categoryName;
+ _mqttNetTrace = mqttNetTrace;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (formatter == null)
+ {
+ throw new ArgumentNullException(nameof(formatter));
+ }
+
+ var message = formatter(state, exception);
+
+ var traceMessage = new MqttNetTraceMessage(DateTime.Now, Environment.CurrentManagedThreadId, _categoryName, logLevel, message, exception);
+ _mqttNetTrace.Publish(traceMessage);
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return true;
+ }
+
+ //not supported: async local requires netstandard1.3
+ //for implementation see https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Console/ConsoleLogScope.cs
+ public IDisposable BeginScope(TState state)
+ {
+ return new DisposableScope();
+ }
+
+ private class DisposableScope : IDisposable
+ {
+ public void Dispose()
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs
index 4ed4619c..c14d4931 100644
--- a/MQTTnet.Core/Diagnostics/MqttNetTrace.cs
+++ b/MQTTnet.Core/Diagnostics/MqttNetTrace.cs
@@ -1,63 +1,33 @@
using System;
+using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Core.Diagnostics
{
- public static class MqttNetTrace
+ public class MqttNetTrace : ILoggerProvider
{
+ private readonly ConcurrentDictionary _loggers = new ConcurrentDictionary();
+
public static event EventHandler TraceMessagePublished;
- public static void Verbose(string source, string message, params object[] parameters)
+ public void Publish(MqttNetTraceMessage msg)
{
- Publish(source, MqttNetTraceLevel.Verbose, null, message, parameters);
+ TraceMessagePublished?.Invoke(this, new MqttNetTraceMessagePublishedEventArgs(msg));
}
- public static void Information(string source, string message, params object[] parameters)
+ public void Dispose()
{
- Publish(source, MqttNetTraceLevel.Information, null, message, parameters);
+ TraceMessagePublished = null;
}
- public static void Warning(string source, string message, params object[] parameters)
+ public ILogger CreateLogger(string categoryName)
{
- Publish(source, MqttNetTraceLevel.Warning, null, message, parameters);
+ return _loggers.GetOrAdd(categoryName, CreateLoggerImplementation);
}
- public static void Warning(string source, Exception exception, string message, params object[] parameters)
+ private MqttNetLogger CreateLoggerImplementation(string categoryName)
{
- Publish(source, MqttNetTraceLevel.Warning, exception, message, parameters);
- }
-
- public static void Error(string source, string message, params object[] parameters)
- {
- Publish(source, MqttNetTraceLevel.Error, null, message, parameters);
- }
-
- public static void Error(string source, Exception exception, string message, params object[] parameters)
- {
- Publish(source, MqttNetTraceLevel.Error, exception, message, parameters);
- }
-
- private static void Publish(string source, MqttNetTraceLevel traceLevel, Exception exception, string message, params object[] parameters)
- {
- var handler = TraceMessagePublished;
- if (handler == null)
- {
- return;
- }
-
- if (parameters?.Length > 0)
- {
- try
- {
- message = string.Format(message, parameters);
- }
- catch (Exception formatException)
- {
- Error(nameof(MqttNetTrace), formatException, "Error while tracing message: " + message);
- return;
- }
- }
-
- handler.Invoke(null, new MqttNetTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception));
+ return new MqttNetLogger(categoryName, this);
}
}
}
diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs
deleted file mode 100644
index 5f3a13f4..00000000
--- a/MQTTnet.Core/Diagnostics/MqttNetTraceLevel.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace MQTTnet.Core.Diagnostics
-{
- public enum MqttNetTraceLevel
- {
- Verbose,
- Information,
- Warning,
- Error
- }
-}
diff --git a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs
index 05d53962..5b8f796d 100644
--- a/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs
+++ b/MQTTnet.Core/Diagnostics/MqttNetTraceMessagePublishedEventArgs.cs
@@ -4,23 +4,11 @@ namespace MQTTnet.Core.Diagnostics
{
public sealed class MqttNetTraceMessagePublishedEventArgs : EventArgs
{
- public MqttNetTraceMessagePublishedEventArgs(int threadId, string source, MqttNetTraceLevel level, string message, Exception exception)
+ public MqttNetTraceMessagePublishedEventArgs(MqttNetTraceMessage traceMessage)
{
- ThreadId = threadId;
- Source = source;
- Level = level;
- Message = message;
- Exception = exception;
+ TraceMessage = traceMessage ?? throw new ArgumentNullException(nameof(traceMessage));
}
- public int ThreadId { get; }
-
- public string Source { get; }
-
- public MqttNetTraceLevel Level { get; }
-
- public string Message { get; }
-
- public Exception Exception { get; }
+ public MqttNetTraceMessage TraceMessage { get; }
}
}
diff --git a/MQTTnet.Core/Diagnostics/MqttTraceMessage.cs b/MQTTnet.Core/Diagnostics/MqttTraceMessage.cs
new file mode 100644
index 00000000..852aae45
--- /dev/null
+++ b/MQTTnet.Core/Diagnostics/MqttTraceMessage.cs
@@ -0,0 +1,30 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace MQTTnet.Core.Diagnostics
+{
+ public sealed class MqttNetTraceMessage
+ {
+ public MqttNetTraceMessage(DateTime timestamp, int threadId, string source, LogLevel level, string message, Exception exception)
+ {
+ Timestamp = timestamp;
+ ThreadId = threadId;
+ Source = source;
+ Level = level;
+ Message = message;
+ Exception = exception;
+ }
+
+ public DateTime Timestamp { get; }
+
+ public int ThreadId { get; }
+
+ public string Source { get; }
+
+ public LogLevel Level { get; }
+
+ public string Message { get; }
+
+ public Exception Exception { get; }
+ }
+}
diff --git a/MQTTnet.Core/Exceptions/MqttCommunicationException.cs b/MQTTnet.Core/Exceptions/MqttCommunicationException.cs
index 2fc578e9..e019f4ee 100644
--- a/MQTTnet.Core/Exceptions/MqttCommunicationException.cs
+++ b/MQTTnet.Core/Exceptions/MqttCommunicationException.cs
@@ -17,10 +17,5 @@ namespace MQTTnet.Core.Exceptions
: base(message)
{
}
-
- public MqttCommunicationException(string message, Exception innerException)
- : base(message, innerException)
- {
- }
}
}
diff --git a/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs b/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs
index 7d3c6658..7ca7d005 100644
--- a/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs
+++ b/MQTTnet.Core/Internal/MqttApplicationMessageExtensions.cs
@@ -6,12 +6,13 @@ namespace MQTTnet.Core.Internal
{
public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket)
{
- return new MqttApplicationMessage(
- publishPacket.Topic,
- publishPacket.Payload,
- publishPacket.QualityOfServiceLevel,
- publishPacket.Retain
- );
+ return new MqttApplicationMessage
+ {
+ Topic = publishPacket.Topic,
+ Payload = publishPacket.Payload,
+ QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
+ Retain = publishPacket.Retain
+ };
}
public static MqttPublishPacket ToPublishPacket(this MqttApplicationMessage applicationMessage)
diff --git a/MQTTnet.Core/MQTTnet.Core.csproj b/MQTTnet.Core/MQTTnet.Core.csproj
index 6997edce..71a518d5 100644
--- a/MQTTnet.Core/MQTTnet.Core.csproj
+++ b/MQTTnet.Core/MQTTnet.Core.csproj
@@ -17,19 +17,15 @@
- 2.4.0.0
- 2.4.0.0
+ 2.5.0.0
+ 2.5.0.0
-
-
-
-
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs b/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs
new file mode 100644
index 00000000..298de9a1
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/IManagedMqttClientOptions.cs
@@ -0,0 +1,14 @@
+using System;
+using MQTTnet.Core.Client;
+
+namespace MQTTnet.Core.ManagedClient
+{
+ public interface IManagedMqttClientOptions
+ {
+ IMqttClientOptions ClientOptions { get; }
+
+ TimeSpan AutoReconnectDelay { get; }
+
+ IManagedMqttClientStorage Storage { get; }
+ }
+}
\ No newline at end of file
diff --git a/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs b/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs
new file mode 100644
index 00000000..39ea89cc
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/IManagedMqttClientStorage.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Core.ManagedClient
+{
+ public interface IManagedMqttClientStorage
+ {
+ Task SaveQueuedMessagesAsync(IList messages);
+
+ Task> LoadQueuedMessagesAsync();
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs
new file mode 100644
index 00000000..6b2e1dd0
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/ManagedMqttClient.cs
@@ -0,0 +1,304 @@
+using System;
+using System.Collections.Concurrent;
+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.Packets;
+using MQTTnet.Core.Protocol;
+using Microsoft.Extensions.Logging;
+
+namespace MQTTnet.Core.ManagedClient
+{
+ public class ManagedMqttClient : IApplicationMessageReceiver
+ {
+ private readonly ManagedMqttClientStorageManager _storageManager = new ManagedMqttClientStorageManager();
+ private readonly BlockingCollection _messageQueue = new BlockingCollection();
+ private readonly HashSet _subscriptions = new HashSet();
+
+ private readonly IMqttClient _mqttClient;
+ private readonly ILogger _logger;
+
+ private CancellationTokenSource _connectionCancellationToken;
+ private CancellationTokenSource _publishingCancellationToken;
+
+ private IManagedMqttClientOptions _options;
+ private bool _subscriptionsNotPushed;
+
+ public ManagedMqttClient(ILogger logger, IMqttClient mqttClient)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
+
+ _mqttClient.Connected += OnConnected;
+ _mqttClient.Disconnected += OnDisconnected;
+ _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
+ }
+
+ public bool IsConnected => _mqttClient.IsConnected;
+
+ public event EventHandler Connected;
+ public event EventHandler Disconnected;
+ public event EventHandler ApplicationMessageReceived;
+
+ public async Task StartAsync(IManagedMqttClientOptions options)
+ {
+ if (options == null) throw new ArgumentNullException(nameof(options));
+ if (options.ClientOptions == null) throw new ArgumentException("The client options are not set.", nameof(options));
+
+ if (!options.ClientOptions.CleanSession)
+ {
+ throw new NotSupportedException("The managed client does not support existing sessions.");
+ }
+
+ 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);
+ }
+ }
+
+ _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);
+#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+
+ _logger.LogInformation("Started");
+ }
+
+ public Task StopAsync()
+ {
+ _connectionCancellationToken?.Cancel(false);
+ _connectionCancellationToken = null;
+
+ while (_messageQueue.Any())
+ {
+ _messageQueue.Take();
+ }
+
+ return Task.FromResult(0);
+ }
+
+ public Task EnqueueAsync(IEnumerable applicationMessages)
+ {
+ if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
+
+ foreach (var applicationMessage in applicationMessages)
+ {
+ _messageQueue.Add(applicationMessage);
+ }
+
+ return Task.FromResult(0);
+ }
+
+ public Task SubscribeAsync(IEnumerable topicFilters)
+ {
+ if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
+
+ lock (_subscriptions)
+ {
+ foreach (var topicFilter in topicFilters)
+ {
+ if (_subscriptions.Add(topicFilter))
+ {
+ _subscriptionsNotPushed = true;
+ }
+ }
+ }
+
+ return Task.FromResult(0);
+ }
+
+ public Task UnsubscribeAsync(IEnumerable topicFilters)
+ {
+ lock (_subscriptions)
+ {
+ foreach (var topicFilter in topicFilters)
+ {
+ if (_subscriptions.Remove(topicFilter))
+ {
+ _subscriptionsNotPushed = true;
+ }
+ }
+ }
+
+ return Task.FromResult(0);
+ }
+
+ private async Task MaintainConnectionAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var connectionState = await ReconnectIfRequiredAsync().ConfigureAwait(false);
+ if (connectionState == ReconnectionResult.NotConnected)
+ {
+ _publishingCancellationToken?.Cancel(false);
+ _publishingCancellationToken = null;
+
+ await Task.Delay(_options.AutoReconnectDelay, cancellationToken).ConfigureAwait(false);
+ continue;
+ }
+
+ if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
+ {
+ await PushSubscriptionsAsync();
+
+ _publishingCancellationToken = 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(() => 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;
+ }
+
+ if (connectionState == ReconnectionResult.StillConnected)
+ {
+ await Task.Delay(100, _connectionCancellationToken.Token).ConfigureAwait(false); // Consider using the _Disconnected_ event here. (TaskCompletionSource)
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (MqttCommunicationException exception)
+ {
+ _logger.LogWarning(new EventId(), 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");
+ }
+ }
+
+ private async Task PublishQueuedMessagesAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var message = _messageQueue.Take(cancellationToken);
+ if (message == null)
+ {
+ continue;
+ }
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ continue;
+ }
+
+ await TryPublishQueuedMessageAsync(message).ConfigureAwait(false);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ finally
+ {
+ _logger.LogInformation("Stopped publishing messages");
+ }
+ }
+
+ private async Task TryPublishQueuedMessageAsync(MqttApplicationMessage message)
+ {
+ try
+ {
+ await _mqttClient.PublishAsync(message).ConfigureAwait(false);
+ }
+ catch (MqttCommunicationException exception)
+ {
+ _logger.LogWarning(new EventId(), exception, "Publishing application message failed.");
+
+ if (message.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
+ {
+ _messageQueue.Add(message);
+ }
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(new EventId(), exception, "Unhandled exception while publishing queued application message.");
+ }
+ }
+
+ private async Task PushSubscriptionsAsync()
+ {
+ _logger.LogInformation(nameof(ManagedMqttClient), "Synchronizing subscriptions");
+
+ List subscriptions;
+ lock (_subscriptions)
+ {
+ subscriptions = _subscriptions.ToList();
+ _subscriptionsNotPushed = false;
+ }
+
+ if (!_subscriptions.Any())
+ {
+ return;
+ }
+
+ try
+ {
+ await _mqttClient.SubscribeAsync(subscriptions).ConfigureAwait(false);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogWarning(new EventId(), exception, "Synchronizing subscriptions failed");
+ _subscriptionsNotPushed = true;
+ }
+ }
+
+ private async Task ReconnectIfRequiredAsync()
+ {
+ if (_mqttClient.IsConnected)
+ {
+ return ReconnectionResult.StillConnected;
+ }
+
+ try
+ {
+ await _mqttClient.ConnectAsync(_options.ClientOptions).ConfigureAwait(false);
+ return ReconnectionResult.Reconnected;
+ }
+ catch (Exception)
+ {
+ return ReconnectionResult.NotConnected;
+ }
+ }
+
+ private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
+ {
+ ApplicationMessageReceived?.Invoke(this, eventArgs);
+ }
+
+ private void OnDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
+ {
+ Disconnected?.Invoke(this, eventArgs);
+ }
+
+ private void OnConnected(object sender, MqttClientConnectedEventArgs eventArgs)
+ {
+ Connected?.Invoke(this, eventArgs);
+ }
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs
new file mode 100644
index 00000000..62aa166f
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientExtensions.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Threading.Tasks;
+using MQTTnet.Core.Packets;
+
+namespace MQTTnet.Core.ManagedClient
+{
+ public static class ManagedMqttClientExtensions
+ {
+ public static Task EnqueueAsync(this ManagedMqttClient managedClient, params MqttApplicationMessage[] applicationMessages)
+ {
+ if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+
+ return managedClient.EnqueueAsync(applicationMessages);
+ }
+
+ public static Task SubscribeAsync(this ManagedMqttClient managedClient, params TopicFilter[] topicFilters)
+ {
+ if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+
+ return managedClient.SubscribeAsync(topicFilters);
+ }
+
+ public static Task UnsubscribeAsync(this ManagedMqttClient managedClient, params TopicFilter[] topicFilters)
+ {
+ if (managedClient == null) throw new ArgumentNullException(nameof(managedClient));
+
+ return managedClient.UnsubscribeAsync(topicFilters);
+ }
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs
new file mode 100644
index 00000000..2e24fd0a
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientOptions.cs
@@ -0,0 +1,14 @@
+using System;
+using MQTTnet.Core.Client;
+
+namespace MQTTnet.Core.ManagedClient
+{
+ public class ManagedMqttClientOptions : IManagedMqttClientOptions
+ {
+ public IMqttClientOptions ClientOptions { get; set; }
+
+ public TimeSpan AutoReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
+
+ public IManagedMqttClientStorage Storage { get; set; }
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs b/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs
new file mode 100644
index 00000000..0810f36e
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/ManagedMqttClientStorageManager.cs
@@ -0,0 +1,75 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Core.ManagedClient
+{
+ public class ManagedMqttClientStorageManager
+ {
+ private readonly List _applicationMessages = new List();
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+ private IManagedMqttClientStorage _storage;
+
+ public async Task SetStorageAsync(IManagedMqttClientStorage storage)
+ {
+ await _semaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ _storage = storage;
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ public async Task AddAsync(MqttApplicationMessage applicationMessage)
+ {
+ await _semaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ if (_storage == null)
+ {
+ return;
+ }
+
+ _applicationMessages.Add(applicationMessage);
+ await SaveAsync().ConfigureAwait(false);
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ public async Task RemoveAsync(MqttApplicationMessage applicationMessage)
+ {
+ await _semaphore.WaitAsync().ConfigureAwait(false);
+ try
+ {
+ if (_storage == null)
+ {
+ return;
+ }
+
+ var index = _applicationMessages.IndexOf(applicationMessage);
+ if (index == -1)
+ {
+ return;
+ }
+
+ _applicationMessages.RemoveAt(index);
+ await SaveAsync().ConfigureAwait(false);
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ private Task SaveAsync()
+ {
+ return _storage.SaveQueuedMessagesAsync(_applicationMessages);
+ }
+ }
+}
diff --git a/MQTTnet.Core/ManagedClient/ReconnectionResult.cs b/MQTTnet.Core/ManagedClient/ReconnectionResult.cs
new file mode 100644
index 00000000..fae07706
--- /dev/null
+++ b/MQTTnet.Core/ManagedClient/ReconnectionResult.cs
@@ -0,0 +1,9 @@
+namespace MQTTnet.Core.ManagedClient
+{
+ public enum ReconnectionResult
+ {
+ StillConnected,
+ Reconnected,
+ NotConnected
+ }
+}
diff --git a/MQTTnet.Core/MqttApplicationMessage.cs b/MQTTnet.Core/MqttApplicationMessage.cs
index 382d702c..b07ee18f 100644
--- a/MQTTnet.Core/MqttApplicationMessage.cs
+++ b/MQTTnet.Core/MqttApplicationMessage.cs
@@ -5,6 +5,11 @@ namespace MQTTnet.Core
{
public sealed class MqttApplicationMessage
{
+ public MqttApplicationMessage()
+ {
+ }
+
+ [Obsolete("Use object initializer or _MqttApplicationMessageBuilder_ instead.")]
public MqttApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
Topic = topic ?? throw new ArgumentNullException(nameof(topic));
@@ -13,12 +18,12 @@ namespace MQTTnet.Core
Retain = retain;
}
- public string Topic { get; }
+ public string Topic { get; set; }
- public byte[] Payload { get; }
+ public byte[] Payload { get; set; }
- public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
+ public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
- public bool Retain { get; }
+ public bool Retain { get; set; }
}
}
diff --git a/MQTTnet.Core/MqttApplicationMessageBuilder.cs b/MQTTnet.Core/MqttApplicationMessageBuilder.cs
new file mode 100644
index 00000000..c2e04318
--- /dev/null
+++ b/MQTTnet.Core/MqttApplicationMessageBuilder.cs
@@ -0,0 +1,114 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using MQTTnet.Core.Exceptions;
+using MQTTnet.Core.Protocol;
+
+namespace MQTTnet.Core
+{
+ public class MqttApplicationMessageBuilder
+ {
+ private MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
+ private string _topic;
+ private byte[] _payload;
+ private bool _retain;
+
+ public MqttApplicationMessageBuilder WithTopic(string topic)
+ {
+ _topic = topic;
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithPayload(IEnumerable payload)
+ {
+ if (payload == null)
+ {
+ _payload = null;
+ return this;
+ }
+
+ _payload = payload.ToArray();
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithPayload(MemoryStream payload)
+ {
+ if (payload == null)
+ {
+ _payload = null;
+ return this;
+ }
+
+ if (payload.Length == 0)
+ {
+ _payload = new byte[0];
+ }
+ else
+ {
+ _payload = new byte[payload.Length - payload.Position];
+ payload.Read(_payload, 0, _payload.Length);
+ }
+
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithPayload(string payload)
+ {
+ if (payload == null)
+ {
+ _payload = null;
+ return this;
+ }
+
+ _payload = string.IsNullOrEmpty(payload) ? new byte[0] : Encoding.UTF8.GetBytes(payload);
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel)
+ {
+ _qualityOfServiceLevel = qualityOfServiceLevel;
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithRetainFlag(bool value = true)
+ {
+ _retain = value;
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithAtLeastOnceQoS()
+ {
+ _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce;
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithAtMostOnceQoS()
+ {
+ _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
+ return this;
+ }
+
+ public MqttApplicationMessageBuilder WithExactlyOnceQoS()
+ {
+ _qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce;
+ return this;
+ }
+
+ public MqttApplicationMessage Build()
+ {
+ if (string.IsNullOrEmpty(_topic))
+ {
+ throw new MqttProtocolViolationException("Topic is not set.");
+ }
+
+ return new MqttApplicationMessage
+ {
+ Topic = _topic,
+ Payload = _payload ?? new byte[0],
+ QualityOfServiceLevel = _qualityOfServiceLevel,
+ Retain = _retain
+ };
+ }
+ }
+}
diff --git a/MQTTnet.Core/MqttApplicationMessageFactory.cs b/MQTTnet.Core/MqttApplicationMessageFactory.cs
deleted file mode 100644
index 55025a31..00000000
--- a/MQTTnet.Core/MqttApplicationMessageFactory.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-using System.Globalization;
-using System.IO;
-using System.Text;
-using MQTTnet.Core.Protocol;
-
-namespace MQTTnet.Core
-{
- public class MqttApplicationMessageFactory
- {
- public MqttApplicationMessage CreateApplicationMessage(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
- {
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- return CreateApplicationMessage(topic, new byte[0], qualityOfServiceLevel, retain);
- }
-
- public MqttApplicationMessage CreateApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
- {
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- if (payload == null)
- {
- payload = new byte[0];
- }
-
- return new MqttApplicationMessage(topic, payload, qualityOfServiceLevel, retain);
- }
-
- public MqttApplicationMessage CreateApplicationMessage(string topic, TPayload payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
- {
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- var payloadString = Convert.ToString(payload, CultureInfo.InvariantCulture);
- var payloadBuffer = string.IsNullOrEmpty(payloadString) ? new byte[0] : Encoding.UTF8.GetBytes(payloadString);
-
- return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain);
- }
-
- public MqttApplicationMessage CreateApplicationMessage(string topic, Stream payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
- {
- if (topic == null) throw new ArgumentNullException(nameof(topic));
-
- byte[] payloadBuffer;
- if (payload == null || payload.Length == 0)
- {
- payloadBuffer = new byte[0];
- }
- else
- {
- payloadBuffer = new byte[payload.Length - payload.Position];
- payload.Read(payloadBuffer, 0, payloadBuffer.Length);
- }
-
- return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain);
- }
- }
-}
diff --git a/MQTTnet.Core/Packets/MqttPacketExtensions.cs b/MQTTnet.Core/Packets/MqttPacketExtensions.cs
index fef10543..6a7e0cea 100644
--- a/MQTTnet.Core/Packets/MqttPacketExtensions.cs
+++ b/MQTTnet.Core/Packets/MqttPacketExtensions.cs
@@ -12,8 +12,7 @@ namespace MQTTnet.Core.Packets
if (responsePacket is IMqttPacketWithIdentifier responsePacketWithIdentifier)
{
- var requestPacketWithIdentifier = packet as IMqttPacketWithIdentifier;
- if (requestPacketWithIdentifier == null)
+ if (!(packet is IMqttPacketWithIdentifier requestPacketWithIdentifier))
{
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
}
diff --git a/MQTTnet.Core/Packets/MqttSubAckPacket.cs b/MQTTnet.Core/Packets/MqttSubAckPacket.cs
index ee577ae1..5882d4c8 100644
--- a/MQTTnet.Core/Packets/MqttSubAckPacket.cs
+++ b/MQTTnet.Core/Packets/MqttSubAckPacket.cs
@@ -8,7 +8,7 @@ namespace MQTTnet.Core.Packets
{
public ushort PacketIdentifier { get; set; }
- public IList SubscribeReturnCodes { get; set; } = new List();
+ public IList SubscribeReturnCodes { get; } = new List();
public override string ToString()
{
diff --git a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs
index 7f070d6b..00906f4a 100644
--- a/MQTTnet.Core/Serializer/MqttPacketSerializer.cs
+++ b/MQTTnet.Core/Serializer/MqttPacketSerializer.cs
@@ -36,81 +36,6 @@ namespace MQTTnet.Core.Serializer
}
}
- private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer)
- {
- if (packet is MqttConnectPacket connectPacket)
- {
- return Serialize(connectPacket, writer);
- }
-
- if (packet is MqttConnAckPacket connAckPacket)
- {
- return Serialize(connAckPacket, writer);
- }
-
- if (packet is MqttDisconnectPacket disconnectPacket)
- {
- return Serialize(disconnectPacket, writer);
- }
-
- if (packet is MqttPingReqPacket pingReqPacket)
- {
- return Serialize(pingReqPacket, writer);
- }
-
- if (packet is MqttPingRespPacket pingRespPacket)
- {
- return Serialize(pingRespPacket, writer);
- }
-
- if (packet is MqttPublishPacket publishPacket)
- {
- return Serialize(publishPacket, writer);
- }
-
- if (packet is MqttPubAckPacket pubAckPacket)
- {
- return Serialize(pubAckPacket, writer);
- }
-
- if (packet is MqttPubRecPacket pubRecPacket)
- {
- return Serialize(pubRecPacket, writer);
- }
-
- if (packet is MqttPubRelPacket pubRelPacket)
- {
- return Serialize(pubRelPacket, writer);
- }
-
- if (packet is MqttPubCompPacket pubCompPacket)
- {
- return Serialize(pubCompPacket, writer);
- }
-
- if (packet is MqttSubscribePacket subscribePacket)
- {
- return Serialize(subscribePacket, writer);
- }
-
- if (packet is MqttSubAckPacket subAckPacket)
- {
- return Serialize(subAckPacket, writer);
- }
-
- if (packet is MqttUnsubscribePacket unsubscribePacket)
- {
- return Serialize(unsubscribePacket, writer);
- }
-
- if (packet is MqttUnsubAckPacket unsubAckPacket)
- {
- return Serialize(unsubAckPacket, writer);
- }
-
- throw new MqttProtocolViolationException("Packet type invalid.");
- }
-
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
{
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
@@ -121,102 +46,90 @@ namespace MQTTnet.Core.Serializer
}
}
+ private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer)
+ {
+ switch (packet)
+ {
+ case MqttConnectPacket connectPacket: return Serialize(connectPacket, writer);
+ case MqttConnAckPacket connAckPacket: return Serialize(connAckPacket, writer);
+ case MqttDisconnectPacket _: return SerializeEmptyPacket(MqttControlPacketType.Disconnect);
+ case MqttPingReqPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingReq);
+ case MqttPingRespPacket _: return SerializeEmptyPacket(MqttControlPacketType.PingResp);
+ case MqttPublishPacket publishPacket: return Serialize(publishPacket, writer);
+ case MqttPubAckPacket pubAckPacket: return Serialize(pubAckPacket, writer);
+ case MqttPubRecPacket pubRecPacket: return Serialize(pubRecPacket, writer);
+ case MqttPubRelPacket pubRelPacket: return Serialize(pubRelPacket, writer);
+ case MqttPubCompPacket pubCompPacket: return Serialize(pubCompPacket, writer);
+ case MqttSubscribePacket subscribePacket: return Serialize(subscribePacket, writer);
+ case MqttSubAckPacket subAckPacket: return Serialize(subAckPacket, writer);
+ case MqttUnsubscribePacket unsubscribePacket: return Serialize(unsubscribePacket, writer);
+ case MqttUnsubAckPacket unsubAckPacket: return Serialize(unsubAckPacket, writer);
+ default: throw new MqttProtocolViolationException("Packet type invalid.");
+ }
+ }
+
private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
{
switch (header.ControlPacketType)
{
- case MqttControlPacketType.Connect:
- {
- return DeserializeConnect(reader);
- }
-
- case MqttControlPacketType.ConnAck:
- {
- return DeserializeConnAck(reader);
- }
-
- case MqttControlPacketType.Disconnect:
- {
- return new MqttDisconnectPacket();
- }
-
- case MqttControlPacketType.Publish:
- {
- return DeserializePublish(reader, header);
- }
-
- case MqttControlPacketType.PubAck:
- {
- return new MqttPubAckPacket
- {
- PacketIdentifier = reader.ReadUInt16()
- };
- }
-
- case MqttControlPacketType.PubRec:
- {
- return new MqttPubRecPacket
- {
- PacketIdentifier = reader.ReadUInt16()
- };
- }
-
- case MqttControlPacketType.PubRel:
- {
- return new MqttPubRelPacket
- {
- PacketIdentifier = reader.ReadUInt16()
- };
- }
-
- case MqttControlPacketType.PubComp:
- {
- return new MqttPubCompPacket
- {
- PacketIdentifier = reader.ReadUInt16()
- };
- }
-
- case MqttControlPacketType.PingReq:
- {
- return new MqttPingReqPacket();
- }
-
- case MqttControlPacketType.PingResp:
- {
- return new MqttPingRespPacket();
- }
-
- case MqttControlPacketType.Subscribe:
- {
- return DeserializeSubscribe(reader);
- }
-
- case MqttControlPacketType.SubAck:
- {
- return DeserializeSubAck(reader);
- }
-
- case MqttControlPacketType.Unsubscibe:
- {
- return DeserializeUnsubscribe(reader);
- }
-
- case MqttControlPacketType.UnsubAck:
- {
- return new MqttUnsubAckPacket
- {
- PacketIdentifier = reader.ReadUInt16()
- };
- }
-
- default:
- {
- throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported.");
- }
+ case MqttControlPacketType.Connect: return DeserializeConnect(reader);
+ case MqttControlPacketType.ConnAck: return DeserializeConnAck(reader);
+ case MqttControlPacketType.Disconnect: return new MqttDisconnectPacket();
+ case MqttControlPacketType.Publish: return DeserializePublish(reader, header);
+ case MqttControlPacketType.PubAck: return DeserializePubAck(reader);
+ case MqttControlPacketType.PubRec: return DeserializePubRec(reader);
+ case MqttControlPacketType.PubRel: return DeserializePubRel(reader);
+ case MqttControlPacketType.PubComp: return DeserializePubComp(reader);
+ case MqttControlPacketType.PingReq: return new MqttPingReqPacket();
+ case MqttControlPacketType.PingResp: return new MqttPingRespPacket();
+ case MqttControlPacketType.Subscribe: return DeserializeSubscribe(reader);
+ case MqttControlPacketType.SubAck: return DeserializeSubAck(reader);
+ case MqttControlPacketType.Unsubscibe: return DeserializeUnsubscribe(reader);
+ case MqttControlPacketType.UnsubAck: return DeserializeUnsubAck(reader);
+ default: throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported.");
}
}
+ private static MqttBasePacket DeserializeUnsubAck(MqttPacketReader reader)
+ {
+ return new MqttUnsubAckPacket
+ {
+ PacketIdentifier = reader.ReadUInt16()
+ };
+ }
+
+ private static MqttBasePacket DeserializePubComp(MqttPacketReader reader)
+ {
+ return new MqttPubCompPacket
+ {
+ PacketIdentifier = reader.ReadUInt16()
+ };
+ }
+
+ private static MqttBasePacket DeserializePubRel(MqttPacketReader reader)
+ {
+ return new MqttPubRelPacket
+ {
+ PacketIdentifier = reader.ReadUInt16()
+ };
+ }
+
+ private static MqttBasePacket DeserializePubRec(MqttPacketReader reader)
+ {
+ return new MqttPubRecPacket
+ {
+ PacketIdentifier = reader.ReadUInt16()
+ };
+ }
+
+ private static MqttBasePacket DeserializePubAck(MqttPacketReader reader)
+ {
+ return new MqttPubAckPacket
+ {
+ PacketIdentifier = reader.ReadUInt16()
+ };
+ }
+
private static MqttBasePacket DeserializeUnsubscribe(MqttPacketReader reader)
{
var packet = new MqttUnsubscribePacket
@@ -297,7 +210,7 @@ namespace MQTTnet.Core.Serializer
throw new MqttProtocolViolationException("Protocol name is not supported.");
}
- var protocolLevel = reader.ReadByte();
+ reader.ReadByte(); // Skip protocol level
var connectFlags = reader.ReadByte();
var connectFlagsReader = new ByteReader(connectFlags);
@@ -320,11 +233,13 @@ namespace MQTTnet.Core.Serializer
if (willFlag)
{
- packet.WillMessage = new MqttApplicationMessage(
- reader.ReadStringWithLengthPrefix(),
- reader.ReadWithLengthPrefix(),
- (MqttQualityOfServiceLevel)willQoS,
- willRetain);
+ packet.WillMessage = new MqttApplicationMessage
+ {
+ Topic = reader.ReadStringWithLengthPrefix(),
+ Payload = reader.ReadWithLengthPrefix(),
+ QualityOfServiceLevel = (MqttQualityOfServiceLevel)willQoS,
+ Retain = willRetain
+ };
}
if (usernameFlag)
@@ -372,6 +287,8 @@ namespace MQTTnet.Core.Serializer
private static void ValidateConnectPacket(MqttConnectPacket packet)
{
+ if (packet == null) throw new ArgumentNullException(nameof(packet));
+
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
{
throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7].");
@@ -380,6 +297,8 @@ namespace MQTTnet.Core.Serializer
private static void ValidatePublishPacket(MqttPublishPacket packet)
{
+ if (packet == null) throw new ArgumentNullException(nameof(packet));
+
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
{
throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
@@ -467,21 +386,6 @@ namespace MQTTnet.Core.Serializer
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
}
- private static byte Serialize(MqttDisconnectPacket packet, MqttPacketWriter writer)
- {
- return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, writer);
- }
-
- private static byte Serialize(MqttPingReqPacket packet, MqttPacketWriter writer)
- {
- return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, writer);
- }
-
- private static byte Serialize(MqttPingRespPacket packet, MqttPacketWriter writer)
- {
- return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, writer);
- }
-
private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter writer)
{
ValidatePublishPacket(packet);
@@ -593,14 +497,13 @@ namespace MQTTnet.Core.Serializer
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
}
- private static byte Serialize(MqttUnsubAckPacket packet, MqttPacketWriter writer)
+ private static byte Serialize(IMqttPacketWithIdentifier packet, BinaryWriter writer)
{
writer.Write(packet.PacketIdentifier);
-
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
}
- private static byte SerializeEmptyPacketAsync(MqttControlPacketType type, MqttPacketWriter writer)
+ private static byte SerializeEmptyPacket(MqttControlPacketType type)
{
return MqttPacketWriter.BuildFixedHeader(type);
}
diff --git a/MQTTnet.Core/Server/IMqttClientSesssionFactory.cs b/MQTTnet.Core/Server/IMqttClientSesssionFactory.cs
new file mode 100644
index 00000000..f58dd5e7
--- /dev/null
+++ b/MQTTnet.Core/Server/IMqttClientSesssionFactory.cs
@@ -0,0 +1,7 @@
+namespace MQTTnet.Core.Server
+{
+ public interface IMqttClientSesssionFactory
+ {
+ MqttClientSession CreateClientSession(string sessionId, MqttClientSessionsManager mqttClientSessionsManager);
+ }
+}
diff --git a/MQTTnet.Core/Server/IMqttServer.cs b/MQTTnet.Core/Server/IMqttServer.cs
index 023feb83..db3e1b0d 100644
--- a/MQTTnet.Core/Server/IMqttServer.cs
+++ b/MQTTnet.Core/Server/IMqttServer.cs
@@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using MQTTnet.Core.Adapter;
namespace MQTTnet.Core.Server
{
public interface IMqttServer
{
- event EventHandler ApplicationMessageReceived;
event EventHandler ClientConnected;
event EventHandler ClientDisconnected;
- IReadOnlyList GetConnectedClients();
+ event EventHandler ApplicationMessageReceived;
+
+ IList GetConnectedClients();
void Publish(MqttApplicationMessage applicationMessage);
Task StartAsync();
diff --git a/MQTTnet.Core/Server/IMqttServerFactory.cs b/MQTTnet.Core/Server/IMqttServerFactory.cs
index 4d33a920..61d022f3 100644
--- a/MQTTnet.Core/Server/IMqttServerFactory.cs
+++ b/MQTTnet.Core/Server/IMqttServerFactory.cs
@@ -2,6 +2,6 @@
{
public interface IMqttServerFactory
{
- IMqttServer CreateMqttServer(MqttServerOptions options);
+ IMqttServer CreateMqttServer();
}
}
\ No newline at end of file
diff --git a/MQTTnet.Core/Adapter/MqttClientConnectedEventArgs.cs b/MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs
similarity index 79%
rename from MQTTnet.Core/Adapter/MqttClientConnectedEventArgs.cs
rename to MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs
index ade4d9d5..282e0f8d 100644
--- a/MQTTnet.Core/Adapter/MqttClientConnectedEventArgs.cs
+++ b/MQTTnet.Core/Server/MqttClientConnectedEventArgs.cs
@@ -1,7 +1,6 @@
-using MQTTnet.Core.Server;
-using System;
+using System;
-namespace MQTTnet.Core.Adapter
+namespace MQTTnet.Core.Server
{
public class MqttClientConnectedEventArgs : EventArgs
{
diff --git a/MQTTnet.Core/Adapter/MqttClientDisconnectedEventArgs.cs b/MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs
similarity index 80%
rename from MQTTnet.Core/Adapter/MqttClientDisconnectedEventArgs.cs
rename to MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs
index 8316d403..b1e3d15b 100644
--- a/MQTTnet.Core/Adapter/MqttClientDisconnectedEventArgs.cs
+++ b/MQTTnet.Core/Server/MqttClientDisconnectedEventArgs.cs
@@ -1,7 +1,6 @@
-using MQTTnet.Core.Server;
-using System;
+using System;
-namespace MQTTnet.Core.Adapter
+namespace MQTTnet.Core.Server
{
public class MqttClientDisconnectedEventArgs : EventArgs
{
diff --git a/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs b/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs
index 6571a758..6b3f0233 100644
--- a/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs
+++ b/MQTTnet.Core/Server/MqttClientPendingMessagesQueue.cs
@@ -3,10 +3,10 @@ using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Core.Server
{
@@ -15,9 +15,11 @@ namespace MQTTnet.Core.Server
private readonly BlockingCollection _pendingPublishPackets = new BlockingCollection();
private readonly MqttClientSession _session;
private readonly MqttServerOptions _options;
+ private readonly ILogger _logger;
- public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session)
+ public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session, ILogger logger)
{
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_session = session ?? throw new ArgumentNullException(nameof(session));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
@@ -50,7 +52,7 @@ namespace MQTTnet.Core.Server
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Unhandled exception while sending pending publish packets.");
+ _logger.LogError(new EventId(), exception, "Unhandled exception while sending pending publish packets.");
}
}
@@ -66,18 +68,18 @@ namespace MQTTnet.Core.Server
{
if (exception is MqttCommunicationTimedOutException)
{
- MqttNetTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to timeout.");
+ _logger.LogWarning(new EventId(), exception, "Sending publish packet failed due to timeout.");
}
else if (exception is MqttCommunicationException)
{
- MqttNetTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to communication exception.");
+ _logger.LogWarning(new EventId(), exception, "Sending publish packet failed due to communication exception.");
}
if (exception is OperationCanceledException)
{
}
else
{
- MqttNetTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed.");
+ _logger.LogError(new EventId(), exception, "Sending publish packet failed.");
}
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
diff --git a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs
index 77b49bf7..68a7b99b 100644
--- a/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs
+++ b/MQTTnet.Core/Server/MqttClientRetainedMessagesManager.cs
@@ -2,20 +2,22 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using MQTTnet.Core.Diagnostics;
-using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
namespace MQTTnet.Core.Server
{
public sealed class MqttClientRetainedMessagesManager
{
- private readonly Dictionary _retainedMessages = new Dictionary();
+ private readonly Dictionary _retainedMessages = new Dictionary();
+ private readonly ILogger _logger;
private readonly MqttServerOptions _options;
- public MqttClientRetainedMessagesManager(MqttServerOptions options)
+ public MqttClientRetainedMessagesManager(IOptions options, ILogger logger)
{
- _options = options ?? throw new ArgumentNullException(nameof(options));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _options = options.Value ?? throw new ArgumentNullException(nameof(options));
}
public async Task LoadMessagesAsync()
@@ -33,35 +35,35 @@ namespace MQTTnet.Core.Server
_retainedMessages.Clear();
foreach (var retainedMessage in retainedMessages)
{
- _retainedMessages[retainedMessage.Topic] = retainedMessage.ToPublishPacket();
+ _retainedMessages[retainedMessage.Topic] = retainedMessage;
}
}
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while loading retained messages.");
+ _logger.LogError(new EventId(), exception, "Unhandled exception while loading retained messages.");
}
}
- public async Task HandleMessageAsync(string clientId, MqttPublishPacket publishPacket)
+ public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage)
{
- if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
+ if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
- List allRetainedMessages;
+ List allRetainedMessages;
lock (_retainedMessages)
{
- if (publishPacket.Payload?.Any() == false)
+ if (applicationMessage.Payload?.Any() == false)
{
- _retainedMessages.Remove(publishPacket.Topic);
- MqttNetTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, publishPacket.Topic);
+ _retainedMessages.Remove(applicationMessage.Topic);
+ _logger.LogInformation("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
}
else
{
- _retainedMessages[publishPacket.Topic] = publishPacket;
- MqttNetTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, publishPacket.Topic);
+ _retainedMessages[applicationMessage.Topic] = applicationMessage;
+ _logger.LogInformation("Client '{0}' updated retained message for topic '{1}'.", clientId, applicationMessage.Topic);
}
- allRetainedMessages = new List(_retainedMessages.Values);
+ allRetainedMessages = new List(_retainedMessages.Values);
}
try
@@ -69,18 +71,18 @@ namespace MQTTnet.Core.Server
// ReSharper disable once UseNullPropagation
if (_options.Storage != null)
{
- await _options.Storage.SaveRetainedMessagesAsync(allRetainedMessages.Select(p => p.ToApplicationMessage()).ToList());
+ await _options.Storage.SaveRetainedMessagesAsync(allRetainedMessages);
}
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while saving retained messages.");
+ _logger.LogError(new EventId(), exception, "Unhandled exception while saving retained messages.");
}
}
- public List GetMessages(MqttSubscribePacket subscribePacket)
+ public List GetMessages(MqttSubscribePacket subscribePacket)
{
- var retainedMessages = new List();
+ var retainedMessages = new List();
lock (_retainedMessages)
{
foreach (var retainedMessage in _retainedMessages.Values)
diff --git a/MQTTnet.Core/Server/MqttClientSession.cs b/MQTTnet.Core/Server/MqttClientSession.cs
index 37d168ae..b519ef28 100644
--- a/MQTTnet.Core/Server/MqttClientSession.cs
+++ b/MQTTnet.Core/Server/MqttClientSession.cs
@@ -3,34 +3,37 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Serializer;
+using Microsoft.Extensions.Logging;
namespace MQTTnet.Core.Server
{
public sealed class MqttClientSession : IDisposable
{
private readonly HashSet _unacknowledgedPublishPackets = new HashSet();
-
+
private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager();
private readonly MqttClientSessionsManager _mqttClientSessionsManager;
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
private readonly MqttServerOptions _options;
+ private readonly ILogger _logger;
private IMqttCommunicationAdapter _adapter;
private CancellationTokenSource _cancellationTokenSource;
private MqttApplicationMessage _willMessage;
- public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager)
+ public MqttClientSession(string clientId, MqttClientSessionsManager mqttClientSessionsManager, ILogger logger, ILogger msgQueueLogger)
{
ClientId = clientId;
- _options = options ?? throw new ArgumentNullException(nameof(options));
+ _options = mqttClientSessionsManager.Options;
_mqttClientSessionsManager = mqttClientSessionsManager ?? throw new ArgumentNullException(nameof(mqttClientSessionsManager));
- _pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this);
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+
+ _pendingMessagesQueue = new MqttClientPendingMessagesQueue(mqttClientSessionsManager.Options, this, msgQueueLogger);
}
public string ClientId { get; }
@@ -58,11 +61,11 @@ namespace MQTTnet.Core.Server
}
catch (MqttCommunicationException exception)
{
- MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
+ _logger.LogWarning(new EventId(), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
+ _logger.LogError(new EventId(), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
}
}
@@ -70,7 +73,7 @@ namespace MQTTnet.Core.Server
{
if (_willMessage != null)
{
- _mqttClientSessionsManager.DispatchPublishPacket(this, _willMessage.ToPublishPacket());
+ _mqttClientSessionsManager.DispatchApplicationMessage(this, _willMessage);
}
_cancellationTokenSource?.Cancel(false);
@@ -79,7 +82,7 @@ namespace MQTTnet.Core.Server
_adapter = null;
- MqttNetTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", ClientId);
+ _logger.LogInformation("Client '{0}': Disconnected.", ClientId);
}
public void EnqueuePublishPacket(MqttPublishPacket publishPacket)
@@ -92,7 +95,7 @@ namespace MQTTnet.Core.Server
}
_pendingMessagesQueue.Enqueue(publishPacket);
- MqttNetTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", ClientId);
+ _logger.LogTrace("Client '{0}': Enqueued pending publish packet.", ClientId);
}
public void Dispose()
@@ -116,12 +119,12 @@ namespace MQTTnet.Core.Server
}
catch (MqttCommunicationException exception)
{
- MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
+ _logger.LogWarning(new EventId(), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
Stop();
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
+ _logger.LogError(new EventId(), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
Stop();
}
}
@@ -163,7 +166,7 @@ namespace MQTTnet.Core.Server
}
else
{
- MqttNetTrace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
+ _logger.LogWarning("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
Stop();
}
}
@@ -173,45 +176,54 @@ namespace MQTTnet.Core.Server
var retainedMessages = _mqttClientSessionsManager.RetainedMessagesManager.GetMessages(subscribePacket);
foreach (var publishPacket in retainedMessages)
{
- EnqueuePublishPacket(publishPacket);
+ EnqueuePublishPacket(publishPacket.ToPublishPacket());
}
}
private async Task HandleIncomingPublishPacketAsync(IMqttCommunicationAdapter adapter, MqttPublishPacket publishPacket)
{
- if (publishPacket.Retain)
+ var applicationMessage = publishPacket.ToApplicationMessage();
+ _options.ApplicationMessageInterceptor?.Invoke(applicationMessage);
+
+ if (applicationMessage.Retain)
{
- await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(ClientId, publishPacket);
+ await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(ClientId, applicationMessage);
}
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
+ switch (applicationMessage.QualityOfServiceLevel)
{
- _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
- return;
+ case MqttQualityOfServiceLevel.AtMostOnce:
+ {
+ _mqttClientSessionsManager.DispatchApplicationMessage(this, applicationMessage);
+ return;
+ }
+ case MqttQualityOfServiceLevel.AtLeastOnce:
+ {
+ _mqttClientSessionsManager.DispatchApplicationMessage(this, applicationMessage);
+
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token,
+ new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
+
+ return;
+ }
+ case MqttQualityOfServiceLevel.ExactlyOnce:
+ {
+ // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
+ lock (_unacknowledgedPublishPackets)
+ {
+ _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
+ }
+
+ _mqttClientSessionsManager.DispatchApplicationMessage(this, applicationMessage);
+
+ await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token,
+ new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
+
+ return;
+ }
+ default:
+ throw new MqttCommunicationException("Received a not supported QoS level.");
}
-
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
- {
- _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
- await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
- return;
- }
-
- if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
- {
- // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
- lock (_unacknowledgedPublishPackets)
- {
- _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
- }
-
- _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
-
- await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
- return;
- }
-
- throw new MqttCommunicationException("Received a not supported QoS level.");
}
private Task HandleIncomingPubRelPacketAsync(IMqttCommunicationAdapter adapter, MqttPubRelPacket pubRelPacket)
diff --git a/MQTTnet.Core/Server/MqttClientSessionsManager.cs b/MQTTnet.Core/Server/MqttClientSessionsManager.cs
index f78a5417..f1840eb4 100644
--- a/MQTTnet.Core/Server/MqttClientSessionsManager.cs
+++ b/MQTTnet.Core/Server/MqttClientSessionsManager.cs
@@ -4,24 +4,28 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Serializer;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
namespace MQTTnet.Core.Server
{
public sealed class MqttClientSessionsManager
{
private readonly Dictionary _clientSessions = new Dictionary();
- private readonly MqttServerOptions _options;
+ private readonly ILogger _logger;
+ private readonly IMqttClientSesssionFactory _mqttClientSesssionFactory;
- public MqttClientSessionsManager(MqttServerOptions options)
+ public MqttClientSessionsManager(IOptions options, ILogger logger, MqttClientRetainedMessagesManager retainedMessagesManager, IMqttClientSesssionFactory mqttClientSesssionFactory)
{
- _options = options ?? throw new ArgumentNullException(nameof(options));
- RetainedMessagesManager = new MqttClientRetainedMessagesManager(options);
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ Options = options.Value ?? throw new ArgumentNullException(nameof(options));
+ RetainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(options));
+ _mqttClientSesssionFactory = mqttClientSesssionFactory ?? throw new ArgumentNullException(nameof(mqttClientSesssionFactory));
}
public event EventHandler ApplicationMessageReceived;
@@ -29,13 +33,14 @@ namespace MQTTnet.Core.Server
public event EventHandler ClientDisconnected;
public MqttClientRetainedMessagesManager RetainedMessagesManager { get; }
+ public MqttServerOptions Options { get; }
public async Task RunClientSessionAsync(IMqttCommunicationAdapter clientAdapter)
{
var clientId = string.Empty;
try
{
- if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket))
+ if (!(await clientAdapter.ReceivePacketAsync(Options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket))
{
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
}
@@ -48,7 +53,7 @@ namespace MQTTnet.Core.Server
var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
- await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
+ await clientAdapter.SendPacketsAsync(Options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode
}).ConfigureAwait(false);
@@ -58,7 +63,7 @@ namespace MQTTnet.Core.Server
var clientSession = GetOrCreateClientSession(connectPacket);
- await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
+ await clientAdapter.SendPacketsAsync(Options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode,
IsSessionPresent = clientSession.IsExistingSession
@@ -70,17 +75,20 @@ namespace MQTTnet.Core.Server
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
}));
- await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false);
+ using (_logger.BeginScope(clientId))
+ {
+ await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false);
+ }
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttServer), exception, exception.Message);
+ _logger.LogError(new EventId(), exception, exception.Message);
}
finally
{
try
{
- await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
+ await clientAdapter.DisconnectAsync(Options.DefaultCommunicationTimeout).ConfigureAwait(false);
}
catch (Exception)
{
@@ -103,7 +111,7 @@ namespace MQTTnet.Core.Server
}
}
- public IReadOnlyList GetConnectedClients()
+ public IList GetConnectedClients()
{
lock (_clientSessions)
{
@@ -115,32 +123,32 @@ namespace MQTTnet.Core.Server
}
}
- public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
+ public void DispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{
try
{
- var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, publishPacket.ToApplicationMessage());
+ var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, applicationMessage);
ApplicationMessageReceived?.Invoke(this, eventArgs);
}
catch (Exception exception)
{
- MqttNetTrace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message");
+ _logger.LogError(new EventId(), exception, "Error while processing application message");
}
lock (_clientSessions)
{
foreach (var clientSession in _clientSessions.Values.ToList())
{
- clientSession.EnqueuePublishPacket(publishPacket);
+ clientSession.EnqueuePublishPacket(applicationMessage.ToPublishPacket());
}
}
}
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
{
- if (_options.ConnectionValidator != null)
+ if (Options.ConnectionValidator != null)
{
- return _options.ConnectionValidator(connectPacket);
+ return Options.ConnectionValidator(connectPacket);
}
return MqttConnectReturnCode.ConnectionAccepted;
@@ -158,11 +166,11 @@ namespace MQTTnet.Core.Server
_clientSessions.Remove(connectPacket.ClientId);
clientSession.Dispose();
clientSession = null;
- MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Disposed existing session of client '{0}'.", connectPacket.ClientId);
+ _logger.LogTrace("Disposed existing session of client '{0}'.", connectPacket.ClientId);
}
else
{
- MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Reusing existing session of client '{0}'.", connectPacket.ClientId);
+ _logger.LogTrace("Reusing existing session of client '{0}'.", connectPacket.ClientId);
}
}
@@ -171,10 +179,10 @@ namespace MQTTnet.Core.Server
{
isExistingSession = false;
- clientSession = new MqttClientSession(connectPacket.ClientId, _options, this);
+ clientSession = _mqttClientSesssionFactory.CreateClientSession(connectPacket.ClientId, this);
_clientSessions[connectPacket.ClientId] = clientSession;
- MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId);
+ _logger.LogTrace("Created a new session for client '{0}'.", connectPacket.ClientId);
}
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
diff --git a/MQTTnet.Core/Server/MqttServer.cs b/MQTTnet.Core/Server/MqttServer.cs
index 2fa72057..0d208c09 100644
--- a/MQTTnet.Core/Server/MqttServer.cs
+++ b/MQTTnet.Core/Server/MqttServer.cs
@@ -3,31 +3,39 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Diagnostics;
-using MQTTnet.Core.Internal;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.Linq;
namespace MQTTnet.Core.Server
{
- public sealed class MqttServer : IMqttServer
+ public class MqttServer : IMqttServer
{
+ private readonly ILogger _logger;
private readonly MqttClientSessionsManager _clientSessionsManager;
private readonly ICollection _adapters;
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
- public MqttServer(MqttServerOptions options, ICollection adapters)
+ public MqttServer(IOptions options, IEnumerable adapters, ILogger logger, MqttClientSessionsManager clientSessionsManager)
{
- _options = options ?? throw new ArgumentNullException(nameof(options));
- _adapters = adapters ?? throw new ArgumentNullException(nameof(adapters));
+ _options = options.Value ?? throw new ArgumentNullException(nameof(options));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
+
+ if (adapters == null)
+ {
+ throw new ArgumentNullException(nameof(adapters));
+ }
+ _adapters = adapters.ToList();
- _clientSessionsManager = new MqttClientSessionsManager(options);
_clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e);
_clientSessionsManager.ClientConnected += OnClientConnected;
_clientSessionsManager.ClientDisconnected += OnClientDisconnected;
}
- public IReadOnlyList GetConnectedClients()
+ public IList GetConnectedClients()
{
return _clientSessionsManager.GetConnectedClients();
}
@@ -40,7 +48,8 @@ namespace MQTTnet.Core.Server
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
- _clientSessionsManager.DispatchPublishPacket(null, applicationMessage.ToPublishPacket());
+ _options.ApplicationMessageInterceptor?.Invoke(applicationMessage);
+ _clientSessionsManager.DispatchApplicationMessage(null, applicationMessage);
}
public async Task StartAsync()
@@ -57,7 +66,7 @@ namespace MQTTnet.Core.Server
await adapter.StartAsync(_options);
}
- MqttNetTrace.Information(nameof(MqttServer), "Started.");
+ _logger.LogInformation("Started.");
}
public async Task StopAsync()
@@ -74,23 +83,23 @@ namespace MQTTnet.Core.Server
_clientSessionsManager.Clear();
- MqttNetTrace.Information(nameof(MqttServer), "Stopped.");
+ _logger.LogInformation("Stopped.");
}
- private void OnClientAccepted(IMqttCommunicationAdapter adapter)
+ private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
{
- Task.Run(() =>_clientSessionsManager.RunClientSessionAsync(adapter), _cancellationTokenSource.Token);
+ eventArgs.SessionTask = Task.Run(async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client), _cancellationTokenSource.Token);
}
private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs)
{
- MqttNetTrace.Information(nameof(MqttServer), "Client '{0}': Connected.", eventArgs.Client.ClientId);
+ _logger.LogInformation("Client '{0}': Connected.", eventArgs.Client.ClientId);
ClientConnected?.Invoke(this, eventArgs);
}
private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
{
- MqttNetTrace.Information(nameof(MqttServer), "Client '{0}': Disconnected.", eventArgs.Client.ClientId);
+ _logger.LogInformation("Client '{0}': Disconnected.", eventArgs.Client.ClientId);
ClientDisconnected?.Invoke(this, eventArgs);
}
}
diff --git a/MQTTnet.Core/Server/MqttServerOptions.cs b/MQTTnet.Core/Server/MqttServerOptions.cs
index c3673b9d..4c6f7adc 100644
--- a/MQTTnet.Core/Server/MqttServerOptions.cs
+++ b/MQTTnet.Core/Server/MqttServerOptions.cs
@@ -16,6 +16,8 @@ namespace MQTTnet.Core.Server
public Func ConnectionValidator { get; set; }
+ public Func ApplicationMessageInterceptor { get; set; }
+
public IMqttServerStorage Storage { get; set; }
}
}
diff --git a/MQTTnet.Core/Server/MqttTopicFilterComparer.cs b/MQTTnet.Core/Server/MqttTopicFilterComparer.cs
index b74852cf..987a71f1 100644
--- a/MQTTnet.Core/Server/MqttTopicFilterComparer.cs
+++ b/MQTTnet.Core/Server/MqttTopicFilterComparer.cs
@@ -4,7 +4,7 @@ namespace MQTTnet.Core.Server
{
public static class MqttTopicFilterComparer
{
- private const char TopicLevelSeparator = '/';
+ private static readonly char[] TopicLevelSeparator = { '/' };
public static bool IsMatch(string topic, string filter)
{
@@ -16,19 +16,15 @@ namespace MQTTnet.Core.Server
return true;
}
- var fragmentsTopic = topic.Split(new[] { TopicLevelSeparator }, StringSplitOptions.None);
- var fragmentsFilter = filter.Split(new[] { TopicLevelSeparator }, StringSplitOptions.None);
+ var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
+ var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
for (var i = 0; i < fragmentsFilter.Length; i++)
{
- if (fragmentsFilter[i] == "+")
+ switch (fragmentsFilter[i])
{
- continue;
- }
-
- if (fragmentsFilter[i] == "#" && i == fragmentsFilter.Length - 1)
- {
- return true;
+ case "+": continue;
+ case "#" when i == fragmentsFilter.Length - 1: return true;
}
if (i >= fragmentsTopic.Length)
@@ -36,18 +32,13 @@ namespace MQTTnet.Core.Server
return false;
}
- if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i]))
+ if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
{
return false;
}
}
- if (fragmentsTopic.Length > fragmentsFilter.Length)
- {
- return false;
- }
-
- return true;
+ return fragmentsTopic.Length <= fragmentsFilter.Length;
}
}
}
diff --git a/MQTTnet.Core/Packets/TopicFilter.cs b/MQTTnet.Core/TopicFilter.cs
similarity index 56%
rename from MQTTnet.Core/Packets/TopicFilter.cs
rename to MQTTnet.Core/TopicFilter.cs
index e2ae4223..3e95afa0 100644
--- a/MQTTnet.Core/Packets/TopicFilter.cs
+++ b/MQTTnet.Core/TopicFilter.cs
@@ -1,6 +1,6 @@
using MQTTnet.Core.Protocol;
-namespace MQTTnet.Core.Packets
+namespace MQTTnet.Core
{
public sealed class TopicFilter
{
@@ -13,5 +13,15 @@ namespace MQTTnet.Core.Packets
public string Topic { get; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
+
+ public override int GetHashCode()
+ {
+ return QualityOfServiceLevel.GetHashCode() ^ (Topic ?? string.Empty).GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return Topic + "@" + QualityOfServiceLevel;
+ }
}
}
\ No newline at end of file
diff --git a/MQTTnet.Core/TopicFilterBuilder.cs b/MQTTnet.Core/TopicFilterBuilder.cs
new file mode 100644
index 00000000..90114e7c
--- /dev/null
+++ b/MQTTnet.Core/TopicFilterBuilder.cs
@@ -0,0 +1,51 @@
+using MQTTnet.Core.Protocol;
+using MQTTnet.Core.Exceptions;
+
+namespace MQTTnet.Core
+{
+ public class TopicFilterBuilder
+ {
+ private string _topic;
+ private MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
+
+ public TopicFilterBuilder WithTopic(string topic)
+ {
+ _topic = topic;
+ return this;
+ }
+
+ public TopicFilterBuilder WithQualityOfServiceLevel(MqttQualityOfServiceLevel qualityOfServiceLevel)
+ {
+ _qualityOfServiceLevel = qualityOfServiceLevel;
+ return this;
+ }
+
+ public TopicFilterBuilder WithAtLeastOnceQoS()
+ {
+ _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce;
+ return this;
+ }
+
+ public TopicFilterBuilder WithAtMostOnceQoS()
+ {
+ _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
+ return this;
+ }
+
+ public TopicFilterBuilder WithExactlyOnceQoS()
+ {
+ _qualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce;
+ return this;
+ }
+
+ public TopicFilter Build()
+ {
+ if (string.IsNullOrEmpty(_topic))
+ {
+ throw new MqttProtocolViolationException("Topic is not set.");
+ }
+
+ return new TopicFilter(_topic, _qualityOfServiceLevel);
+ }
+ }
+}
diff --git a/MQTTnet.sln b/MQTTnet.sln
index 8ba4c60b..263fee0e 100644
--- a/MQTTnet.sln
+++ b/MQTTnet.sln
@@ -1,12 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26730.16
+VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.NetFramework", "Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj", "{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.UniversalWindows", "Frameworks\MQTTnet.UniversalWindows\MQTTnet.UniversalWindows.csproj", "{BD60C727-D8E8-40C3-B8E3-C95A864AE611}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9248C2E1-B9D6-40BF-81EC-86004D7765B4}"
@@ -15,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Frameworks", "Frameworks",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core", "MQTTnet.Core\MQTTnet.Core.csproj", "{2ECB99E4-72D0-4C23-99BA-93D511D3967D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.NetFramework", "Tests\MQTTnet.TestApp.NetFramework\MQTTnet.TestApp.NetFramework.csproj", "{D9D74F33-6943-49B2-B765-7BD589082098}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.TestApp.UniversalWindows", "Tests\MQTTnet.TestApp.UniversalWindows\MQTTnet.TestApp.UniversalWindows.csproj", "{FF1F72D6-9524-4422-9497-3CC0002216ED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.NetStandard", "Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}"
@@ -29,11 +25,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
ProjectSection(SolutionItems) = preProject
+ LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", "Tests\MQTTnet.TestApp.NetCore\MQTTnet.TestApp.NetCore.csproj", "{3D283AAD-AAA8-4339-8394-52F80B6304DB}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.AspNetCore2", "Tests\MQTTnet.TestApp.AspNetCore2\MQTTnet.TestApp.AspNetCore2.csproj", "{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.AspnetCore", "Frameworks\MQTTnet.AspnetCore\MQTTnet.AspnetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -62,22 +63,6 @@ Global
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|ARM.Build.0 = Debug|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x64.ActiveCfg = Debug|x64
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x64.Build.0 = Debug|x64
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x86.ActiveCfg = Debug|x86
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|x86.Build.0 = Debug|x86
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|Any CPU.Build.0 = Release|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|ARM.ActiveCfg = Release|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|ARM.Build.0 = Release|Any CPU
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x64.ActiveCfg = Release|x64
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x64.Build.0 = Release|x64
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x86.ActiveCfg = Release|x86
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Release|x86.Build.0 = Release|x86
{BD60C727-D8E8-40C3-B8E3-C95A864AE611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD60C727-D8E8-40C3-B8E3-C95A864AE611}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD60C727-D8E8-40C3-B8E3-C95A864AE611}.Debug|ARM.ActiveCfg = Debug|ARM
@@ -110,22 +95,6 @@ Global
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x64.Build.0 = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.ActiveCfg = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.Build.0 = Release|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|ARM.Build.0 = Debug|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x64.ActiveCfg = Debug|x64
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x64.Build.0 = Debug|x64
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x86.ActiveCfg = Debug|x86
- {D9D74F33-6943-49B2-B765-7BD589082098}.Debug|x86.Build.0 = Debug|x86
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|Any CPU.Build.0 = Release|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|ARM.ActiveCfg = Release|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|ARM.Build.0 = Release|Any CPU
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x64.ActiveCfg = Release|x64
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x64.Build.0 = Release|x64
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x86.ActiveCfg = Release|x86
- {D9D74F33-6943-49B2-B765-7BD589082098}.Release|x86.Build.0 = Release|x86
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF1F72D6-9524-4422-9497-3CC0002216ED}.Debug|ARM.ActiveCfg = Debug|ARM
@@ -180,18 +149,50 @@ Global
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x64.Build.0 = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.ActiveCfg = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.Build.0 = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|ARM.Build.0 = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|ARM.ActiveCfg = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|ARM.Build.0 = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x64.Build.0 = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9}.Release|x86.Build.0 = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|ARM.Build.0 = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x64.Build.0 = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|x86.Build.0 = Debug|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|ARM.Build.0 = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x64.ActiveCfg = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x64.Build.0 = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x86.ActiveCfg = Release|Any CPU
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
- {A480EF90-0EAA-4D9A-B271-47A9C47F6F7D} = {32A630A7-2598-41D7-B625-204CD906F5FB}
{BD60C727-D8E8-40C3-B8E3-C95A864AE611} = {32A630A7-2598-41D7-B625-204CD906F5FB}
- {D9D74F33-6943-49B2-B765-7BD589082098} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{FF1F72D6-9524-4422-9497-3CC0002216ED} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB}
{3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
+ {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
+ {F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
diff --git a/README.md b/README.md
index 8b7594d8..2bd607a8 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[](https://bettercodehub.com/)
# MQTTnet
-MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.
+MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.
# Features
@@ -18,21 +18,26 @@ MQTTnet is a .NET library for MQTT based communication. It provides a MQTT clien
* Performance optimized (processing ~27.000 messages / second)*
* Interfaces included for mocking and testing
* Access to internal trace messages
-* Unit tested (62+ tests)
+* Unit tested (67+ tests)
\* Tested on local machine with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetFramework_.
### Client
+* Communication via TCP (+TLS) or WS (WebSocket) supported
+* Included core _MqttClient_ with low level functionality
+* Also included _ManagedMqttClient_ which maintains the connection and subscriptions automatically. Also application messages are queued and re-scheduled for higher QoS levels automatically.
* Rx support (via another project)
-* Communication via TCP (+TLS) or WS (WebSocket)
+* Compatible with Microsoft Azure IoT Hub
### Server (broker)
* List of connected clients available
* Supports connected clients with different protocol versions at the same time
* Able to publish its own messages (no loopback client required)
-* Able to receive every messages (no loopback client required)
+* Able to receive every message (no loopback client required)
* Extensible client credential validation
* Retained messages are supported including persisting via interface methods (own implementation required)
+* WebSockets supported (via ASP.NET Core 2.0)
+* A custom message interceptor can be added which allows transforming or extending every received application message
# Supported frameworks
* .NET Standard 1.3+
@@ -53,12 +58,34 @@ This library is available as a nuget package: https://www.nuget.org/packages/MQT
Please find examples and the documentation at the Wiki of this repository (https://github.com/chkr1011/MQTTnet/wiki).
# Contributions
-If you want to contribute to this project just create a pull request.
+If you want to contribute to this project just create a pull request. But only pull requests which are matching the code style of this library will be accepted. Before creating a pull request please have a look at the library to get an overview of the required style.
# References
This library is used in the following projects:
* MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx)
-* HA4IoT (Open Source Home Automation system for .NET, https://github.com/chkr1011/HA4IoT)
+* Wirehome (Open Source Home Automation system for .NET, https://github.com/chkr1011/Wirehome)
-If you use this library and want to see your project here please let me know.
\ No newline at end of file
+If you use this library and want to see your project here please let me know.
+
+# MIT License
+
+Copyright (c) 2017 Christian Kratky
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs
index 2564e69f..2f0966fb 100644
--- a/Tests/MQTTnet.Core.Tests/ExtensionTests.cs
+++ b/Tests/MQTTnet.Core.Tests/ExtensionTests.cs
@@ -55,7 +55,7 @@ namespace MQTTnet.Core.Tests
{
try
{
- var r = await Task.Run(() =>
+ await Task.Run(() =>
{
var iis = new int[0];
return iis[1];
diff --git a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj
index 2a033595..7ba48954 100644
--- a/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj
+++ b/Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj
@@ -7,6 +7,7 @@
+
diff --git a/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs
new file mode 100644
index 00000000..c6a70174
--- /dev/null
+++ b/Tests/MQTTnet.Core.Tests/MqttApplicationMessageBuilderTests.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Core.Protocol;
+
+namespace MQTTnet.Core.Tests
+{
+ [TestClass]
+ public class MqttApplicationMessageBuilderTests
+ {
+ [TestMethod]
+ public void CreateApplicationMessage_TopicOnly()
+ {
+ var message = new MqttApplicationMessageBuilder().WithTopic("Abc").Build();
+ Assert.AreEqual("Abc", message.Topic);
+ Assert.IsFalse(message.Retain);
+ Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
+ }
+
+ [TestMethod]
+ public void CreateApplicationMessage_TimeStampPayload()
+ {
+ var message = new MqttApplicationMessageBuilder().WithTopic("xyz").WithPayload(TimeSpan.FromSeconds(360).ToString()).Build();
+ Assert.AreEqual("xyz", message.Topic);
+ Assert.IsFalse(message.Retain);
+ Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
+ Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
+ }
+
+ [TestMethod]
+ public void CreateApplicationMessage_StreamPayload()
+ {
+ var stream = new MemoryStream(Encoding.UTF8.GetBytes("xHello")) { Position = 1 };
+
+ var message = new MqttApplicationMessageBuilder().WithTopic("123").WithPayload(stream).Build();
+ Assert.AreEqual("123", message.Topic);
+ Assert.IsFalse(message.Retain);
+ Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
+ Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
+ }
+
+ [TestMethod]
+ public void CreateApplicationMessage_Retained()
+ {
+ var message = new MqttApplicationMessageBuilder().WithTopic("lol").WithRetainFlag().Build();
+ Assert.AreEqual("lol", message.Topic);
+ Assert.IsTrue(message.Retain);
+ Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
+ }
+
+ [TestMethod]
+ public void CreateApplicationMessage_QosLevel2()
+ {
+ var message = new MqttApplicationMessageBuilder().WithTopic("rofl").WithRetainFlag().WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build();
+ Assert.AreEqual("rofl", message.Topic);
+ Assert.IsTrue(message.Retain);
+ Assert.AreEqual(MqttQualityOfServiceLevel.ExactlyOnce, message.QualityOfServiceLevel);
+ }
+ }
+}
diff --git a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs
index c83f83ae..7e3a7dca 100644
--- a/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs
+++ b/Tests/MQTTnet.Core.Tests/MqttCommunicationAdapterFactory.cs
@@ -1,4 +1,5 @@
using MQTTnet.Core.Adapter;
+using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
namespace MQTTnet.Core.Tests
@@ -12,7 +13,12 @@ namespace MQTTnet.Core.Tests
_adapter = adapter;
}
- public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
+ public IMqttCommunicationAdapter CreateClientMqttCommunicationAdapter(IMqttClientOptions options)
+ {
+ return _adapter;
+ }
+
+ public IMqttCommunicationAdapter CreateServerMqttCommunicationAdapter(IMqttCommunicationChannel channel)
{
return _adapter;
}
diff --git a/Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs b/Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs
new file mode 100644
index 00000000..bda3195e
--- /dev/null
+++ b/Tests/MQTTnet.Core.Tests/MqttLoggerProviderTest.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Core.Diagnostics;
+
+namespace MQTTnet.Core.Tests
+{
+ [TestClass]
+ public class MqttLoggerProviderTest
+ {
+ [TestMethod]
+ public void TestLoggerCallback()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging();
+
+ var serviceProvider = serviceCollection.BuildServiceProvider();
+ using ((IDisposable)serviceProvider)
+ {
+ var loggerFactory = serviceProvider.GetRequiredService();
+
+ loggerFactory.AddMqttTrace();
+
+ var expectedMsg = "Hello World!";
+ MqttNetTraceMessage msg = null;
+
+ MqttNetTrace.TraceMessagePublished += (sender, args) =>
+ {
+ msg = args.TraceMessage;
+ };
+
+ var logger = loggerFactory.CreateLogger();
+
+ logger.LogInformation(expectedMsg);
+
+ Assert.AreEqual(expectedMsg, msg.Message);
+
+ var expectedException = new Exception("bad stuff");
+
+ logger.LogError(new EventId(), expectedException, expectedException.Message);
+
+ Assert.AreEqual(expectedException, msg.Exception);
+ Assert.AreEqual(expectedException.Message, msg.Message);
+ }
+ }
+ }
+}
diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs
index de6265f0..bdd07516 100644
--- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs
+++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs
@@ -2,10 +2,8 @@
using System.IO;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Core.Adapter;
-using MQTTnet.Core.Channel;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Serializer;
@@ -55,11 +53,13 @@ namespace MQTTnet.Core.Tests
Username = "USER",
KeepAlivePeriod = 123,
CleanSession = true,
- WillMessage = new MqttApplicationMessage(
- "My/last/will",
- Encoding.UTF8.GetBytes("Good byte."),
- MqttQualityOfServiceLevel.AtLeastOnce,
- true)
+ WillMessage = new MqttApplicationMessage
+ {
+ Topic = "My/last/will",
+ Payload = Encoding.UTF8.GetBytes("Good byte."),
+ QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
+ Retain = true
+ }
};
SerializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw==");
@@ -90,11 +90,13 @@ namespace MQTTnet.Core.Tests
Username = "USER",
KeepAlivePeriod = 123,
CleanSession = true,
- WillMessage = new MqttApplicationMessage(
- "My/last/will",
- Encoding.UTF8.GetBytes("Good byte."),
- MqttQualityOfServiceLevel.AtLeastOnce,
- true)
+ WillMessage = new MqttApplicationMessage
+ {
+ Topic = "My/last/will",
+ Payload = Encoding.UTF8.GetBytes("Good byte."),
+ QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
+ Retain = true
+ }
};
DeserializeAndCompare(p, "EDUABE1RVFQE7gB7AANYWVoADE15L2xhc3Qvd2lsbAAKR29vZCBieXRlLgAEVVNFUgAEUEFTUw==");
@@ -387,45 +389,6 @@ namespace MQTTnet.Core.Tests
DeserializeAndCompare(p, "sAIAew==");
}
-
- public class TestChannel : IMqttCommunicationChannel
- {
- private readonly MemoryStream _stream = new MemoryStream();
-
- public Stream ReceiveStream => _stream;
-
- public Stream RawReceiveStream => _stream;
-
- public Stream SendStream => _stream;
-
- public bool IsConnected { get; } = true;
-
- public TestChannel()
- {
- }
-
- public TestChannel(byte[] initialData)
- {
- _stream.Write(initialData, 0, initialData.Length);
- _stream.Position = 0;
- }
-
- public Task ConnectAsync()
- {
- return Task.FromResult(0);
- }
-
- public Task DisconnectAsync()
- {
- return Task.FromResult(0);
- }
-
- public byte[] ToArray()
- {
- return _stream.ToArray();
- }
- }
-
private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
diff --git a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs
index 0e786dfc..e866ca82 100644
--- a/Tests/MQTTnet.Core.Tests/MqttServerTests.cs
+++ b/Tests/MQTTnet.Core.Tests/MqttServerTests.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Core.Adapter;
@@ -6,6 +8,7 @@ using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Server;
+using Microsoft.Extensions.DependencyInjection;
namespace MQTTnet.Core.Tests
{
@@ -49,10 +52,15 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_WillMessage()
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
- var willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false);
+ var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage);
@@ -73,7 +81,12 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_Unsubscribe()
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -82,7 +95,7 @@ namespace MQTTnet.Core.Tests
var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
- var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false);
+ var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build();
await c2.PublishAsync(message);
Assert.AreEqual(0, receivedMessagesCount);
@@ -109,7 +122,12 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_Publish()
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -117,7 +135,7 @@ namespace MQTTnet.Core.Tests
var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
- var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false);
+ var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build();
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));
s.Publish(message);
@@ -132,7 +150,12 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_NoRetainedMessage()
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -155,7 +178,12 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_RetainedMessage()
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -178,7 +206,12 @@ namespace MQTTnet.Core.Tests
public async Task MqttServer_ClearRetainedMessage()
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -204,7 +237,12 @@ namespace MQTTnet.Core.Tests
var storage = new TestStorage();
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -213,7 +251,7 @@ namespace MQTTnet.Core.Tests
await s.StopAsync();
- s = new MqttServer(new MqttServerOptions { Storage = storage }, new List { serverAdapter });
+ s = services.GetRequiredService();
await s.StartAsync();
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
@@ -228,6 +266,46 @@ namespace MQTTnet.Core.Tests
Assert.AreEqual(1, receivedMessagesCount);
}
+ [TestMethod]
+ public async Task MqttServer_InterceptMessage()
+ {
+ var options = new MqttServerOptions
+ {
+ ApplicationMessageInterceptor = message =>
+ {
+ message.Payload = Encoding.ASCII.GetBytes("extended");
+ return message;
+ }
+ };
+
+ var serverAdapter = new TestMqttServerAdapter();
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
+ await s.StartAsync();
+
+ var c1 = await serverAdapter.ConnectTestClient(s, "c1");
+ var c2 = await serverAdapter.ConnectTestClient(s, "c2");
+ await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build());
+
+ var isIntercepted = false;
+ c2.ApplicationMessageReceived += (sender, args) =>
+ {
+ isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(args.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
+ };
+
+ var m = new MqttApplicationMessageBuilder().WithTopic("test").Build();
+ await c1.PublishAsync(m);
+ await c1.DisconnectAsync();
+
+ await Task.Delay(500);
+
+ Assert.IsTrue(isIntercepted);
+ }
+
private class TestStorage : IMqttServerStorage
{
private IList _messages = new List();
@@ -235,7 +313,7 @@ namespace MQTTnet.Core.Tests
public Task SaveRetainedMessagesAsync(IList messages)
{
_messages = messages;
- return Task.FromResult(0);
+ return Task.CompletedTask;
}
public Task> LoadRetainedMessagesAsync()
@@ -252,7 +330,12 @@ namespace MQTTnet.Core.Tests
int expectedReceivedMessagesCount)
{
var serverAdapter = new TestMqttServerAdapter();
- var s = new MqttServer(new MqttServerOptions(), new List { serverAdapter });
+ var services = new ServiceCollection()
+ .AddMqttServer()
+ .AddSingleton(serverAdapter)
+ .BuildServiceProvider();
+
+ var s = services.GetRequiredService();
await s.StartAsync();
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
@@ -261,8 +344,8 @@ namespace MQTTnet.Core.Tests
var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
- await c1.SubscribeAsync(new TopicFilter(topicFilter, filterQualityOfServiceLevel));
- await c2.PublishAsync(new MqttApplicationMessage(topic, new byte[0], qualityOfServiceLevel, false));
+ await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build());
+ await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
await Task.Delay(500);
await c1.UnsubscribeAsync(topicFilter);
diff --git a/Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs b/Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs
new file mode 100644
index 00000000..0117ed08
--- /dev/null
+++ b/Tests/MQTTnet.Core.Tests/ServiceCollectionTest.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace MQTTnet.Core.Tests
+{
+ [TestClass]
+ public class ServiceCollectionTest
+ {
+ [TestMethod]
+ public void TestCanConstructAllServices()
+ {
+ var services = new ServiceCollection()
+ .AddLogging()
+ .AddMqttServer()
+ .AddMqttClient();
+
+ var serviceProvider = services
+ .BuildServiceProvider();
+
+ foreach (var service in services)
+ {
+ if (service.ServiceType.IsGenericType)
+ {
+ continue;
+ }
+ serviceProvider.GetRequiredService(service.ServiceType);
+ }
+ }
+ }
+}
diff --git a/Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs b/Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs
new file mode 100644
index 00000000..519ef500
--- /dev/null
+++ b/Tests/MQTTnet.Core.Tests/TestClientSessionFactory.cs
@@ -0,0 +1,12 @@
+using MQTTnet.Core.Server;
+
+namespace MQTTnet.Core.Tests
+{
+ public class TestClientSessionFactory : IMqttClientSesssionFactory
+ {
+ public MqttClientSession CreateClientSession(string sessionId, MqttClientSessionsManager mqttClientSessionsManager)
+ {
+ return new MqttClientSession(sessionId, mqttClientSessionsManager, new TestLogger(), new TestLogger());
+ }
+ }
+}
diff --git a/Tests/MQTTnet.Core.Tests/TestLogger.cs b/Tests/MQTTnet.Core.Tests/TestLogger.cs
new file mode 100644
index 00000000..ea597a6e
--- /dev/null
+++ b/Tests/MQTTnet.Core.Tests/TestLogger.cs
@@ -0,0 +1,22 @@
+using Microsoft.Extensions.Logging;
+using System;
+
+namespace MQTTnet.Core.Tests
+{
+ public class TestLogger : ILogger
+ {
+ public IDisposable BeginScope(TState state)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return true;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ }
+ }
+}
diff --git a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs
index 19065646..0655a096 100644
--- a/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs
+++ b/Tests/MQTTnet.Core.Tests/TestMqttServerAdapter.cs
@@ -8,7 +8,7 @@ namespace MQTTnet.Core.Tests
{
public class TestMqttServerAdapter : IMqttServerAdapter
{
- public event Action ClientAccepted;
+ public event EventHandler ClientAccepted;
public async Task ConnectTestClient(IMqttServer server, string clientId, MqttApplicationMessage willMessage = null)
{
@@ -17,7 +17,7 @@ namespace MQTTnet.Core.Tests
adapterA.Partner = adapterB;
adapterB.Partner = adapterA;
- var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA));
+ var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA), new TestLogger(), new MqttPacketDispatcher(new TestLogger()));
var connected = WaitForClientToConnect(server, clientId);
FireClientAcceptedEvent(adapterB);
@@ -31,7 +31,7 @@ namespace MQTTnet.Core.Tests
{
var tcs = new TaskCompletionSource