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 @@ [![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](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(); - void Handler(object sender, MqttClientConnectedEventArgs args) + void Handler(object sender, Server.MqttClientConnectedEventArgs args) { if (args.Client.ClientId == clientId) { @@ -47,7 +47,7 @@ namespace MQTTnet.Core.Tests private void FireClientAcceptedEvent(IMqttCommunicationAdapter adapter) { - ClientAccepted?.Invoke(adapter); + ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter)); } public Task StartAsync(MqttServerOptions options) diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj new file mode 100644 index 00000000..2f042084 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/MQTTnet.TestApp.AspNetCore2.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp2.0 + 2.3 + + + + + + + + + + + + + + + + + + + diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs new file mode 100644 index 00000000..38b8c12d --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Program.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace MQTTnet.TestApp.AspNetCore2 +{ + public static class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + private static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs new file mode 100644 index 00000000..a71978c3 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/Startup.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using MQTTnet.AspnetCore; +using MQTTnet.Core; + +namespace MQTTnet.TestApp.AspNetCore2 +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddHostedMqttServer(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + app.UseMqttEndpoint(); + app.UseMqttServer(async server => + { + var msg = new MqttApplicationMessageBuilder() + .WithPayload("Mqtt is awesome") + .WithTopic("message"); + + while (true) + { + server.Publish(msg.Build()); + await Task.Delay(TimeSpan.FromSeconds(2)); + msg.WithPayload("Mqtt is still awesome at " + DateTime.Now); + } + }); + + app.Use((context, next) => + { + if (context.Request.Path == "/") + { + context.Request.Path = "/Index.html"; + } + + return next(); + }); + + app.UseStaticFiles(); + + + app.UseStaticFiles( new StaticFileOptions() + { + RequestPath = "/node_modules", + FileProvider = new PhysicalFileProvider( Path.Combine(env.ContentRootPath, "node_modules" ) ) + } ); + } + } +} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/package.json b/Tests/MQTTnet.TestApp.AspNetCore2/package.json new file mode 100644 index 00000000..ad48ca10 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/package.json @@ -0,0 +1,14 @@ +{ + "version": "1.0.0", + "name": "mqtt.test", + "private": true, + "devDependencies": { + }, + + "dependencies": { + "mqtt": "2.13.1", + "@types/node": "8.0.46", + "systemjs": "0.20.19", + "typescript": "2.3.4" + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json b/Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json new file mode 100644 index 00000000..5794ac5b --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "noImplicitAny": false, + "noEmitOnError": true, + "removeComments": false, + "sourceMap": true, + "target": "es5" + }, + "exclude": [ + "node_modules" + ] +} diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html new file mode 100644 index 00000000..9cc67e4d --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/Index.html @@ -0,0 +1,35 @@ + + + + + MQTT test + + + + + +
+

+ + + +
+ +
+
    +
    + + + \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts new file mode 100644 index 00000000..3ce98566 --- /dev/null +++ b/Tests/MQTTnet.TestApp.AspNetCore2/wwwroot/app/app.ts @@ -0,0 +1,56 @@ +import { connect } from "mqtt"; + +var client = connect('ws://' + location.host + '/mqtt', + { + clientId: "client1", + }); + +window.onbeforeunload = () => { + client.end(); +}; + +var publishButton = document.getElementById("publish"); +var topicInput = document.getElementById("topic"); +var msgInput = document.getElementById("msg"); +var stateParagraph = document.getElementById("state"); +var msgsList = document.getElementById("msgs"); + +publishButton.onclick = click => { + var topic = topicInput.value; + var msg = msgInput.value; + client.publish(topic, msg); +}; + +client.on('connect', () => { + client.subscribe('#', { qos: 0 }, (err, granted) => { + console.log(err); + }); + client.publish('presence', 'Hello mqtt'); + + stateParagraph.innerText = "connected"; +}); + +client.on("error", e => { + showMsg("error: " + e.message); +}); + +client.on("reconnect", () => { + stateParagraph.innerText = "reconnecting"; +}); + +client.on('message', (topic, message) => { + showMsg(topic + ": " + message.toString()); +}); + +function showMsg(msg: string) { + //console.log(msg); + + var node = document.createElement("LI"); + node.appendChild(document.createTextNode(msg)); + + msgsList.appendChild(node); + + if (msgsList.childElementCount > 20) { + msgsList.removeChild(msgsList.childNodes[0]); + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj index 5871b1e8..4c0f99d2 100644 --- a/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj +++ b/Tests/MQTTnet.TestApp.NetCore/MQTTnet.TestApp.NetCore.csproj @@ -3,12 +3,23 @@ Exe Full - netcoreapp2.0 + netcoreapp2.0;net451 + + + + + + + + 1.1.2 + + + diff --git a/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs new file mode 100644 index 00000000..b54de068 --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/ManagedClientTest.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading.Tasks; +using MQTTnet.Core; +using MQTTnet.Core.Client; +using MQTTnet.Core.ManagedClient; +using MQTTnet.Core.Packets; +using MQTTnet.Core.Protocol; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.TestApp.NetCore +{ + public static class ManagedClientTest + { + public static async Task RunAsync() + { + var services = new ServiceCollection() + .AddMqttClient() + .AddLogging() + .BuildServiceProvider(); + + services.GetService() + .AddConsole(); + + + var options = new ManagedMqttClientOptions + { + ClientOptions = new MqttClientTcpOptions + { + Server = "broker.hivemq.com", + ClientId = "MQTTnetManagedClientTest" + }, + + AutoReconnectDelay = TimeSpan.FromSeconds(1) + }; + + try + { + var managedClient = services.GetRequiredService(); + managedClient.ApplicationMessageReceived += (s, e) => + { + Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); + }; + + await managedClient.EnqueueAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("1").Build()); + await managedClient.EnqueueAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("2").WithAtLeastOnceQoS().Build()); + + await managedClient.StartAsync(options); + + await managedClient.SubscribeAsync(new TopicFilter("xyz", MqttQualityOfServiceLevel.AtMostOnce)); + + await managedClient.EnqueueAsync(new MqttApplicationMessageBuilder().WithTopic("Step").WithPayload("3").Build()); + + Console.WriteLine("Managed client started."); + Console.ReadLine(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} diff --git a/Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs similarity index 76% rename from Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs rename to Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs index 9f44419e..746aa74e 100644 --- a/Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs +++ b/Tests/MQTTnet.TestApp.NetCore/PerformanceTest.cs @@ -1,4 +1,6 @@ -using MQTTnet.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MQTTnet.Core; using MQTTnet.Core.Client; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; @@ -11,39 +13,64 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace MQTTnet.TestApp.NetFramework +namespace MQTTnet.TestApp.NetCore { public static class PerformanceTest { public static async Task RunAsync() { - var server = Task.Factory.StartNew(RunServerAsync, TaskCreationOptions.LongRunning); - var client = Task.Factory.StartNew(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10)), TaskCreationOptions.LongRunning); + var services = new ServiceCollection() + .AddMqttServer(options => { + + options.ConnectionValidator = p => + { + if (p.ClientId == "SpecialClient") + { + if (p.Username != "USER" || p.Password != "PASS") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + } + + return MqttConnectReturnCode.ConnectionAccepted; + }; + + options.DefaultCommunicationTimeout = TimeSpan.FromMinutes(10); + }) + .AddMqttClient() + .AddLogging() + .BuildServiceProvider(); + + services.GetService() + .AddConsole(minLevel: LogLevel.Warning, includeScopes: true); + + Console.WriteLine("Press 'c' for concurrent sends. Otherwise in one batch."); + var concurrent = Console.ReadKey(intercept: true).KeyChar == 'c'; + + var server = Task.Factory.StartNew(() => RunServerAsync(services), TaskCreationOptions.LongRunning); + var client = Task.Factory.StartNew(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10), services, concurrent), TaskCreationOptions.LongRunning); await Task.WhenAll(server, client).ConfigureAwait(false); } - private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval) + private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval, IServiceProvider serviceProvider, bool concurrent) { - return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval)))); + return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval, serviceProvider, concurrent)))); } - private static async Task RunClientAsync(int msgChunkSize, TimeSpan interval) + private static async Task RunClientAsync(int msgChunkSize, TimeSpan interval, IServiceProvider serviceProvider, bool concurrent) { try { var options = new MqttClientTcpOptions { Server = "localhost", - ClientId = "XYZ", + ClientId = "Client1", CleanSession = true, DefaultCommunicationTimeout = TimeSpan.FromMinutes(10) }; - var client = new MqttClientFactory().CreateMqttClient(); - client.ApplicationMessageReceived += (s, e) => - { - }; + var client = serviceProvider.GetRequiredService(); client.Connected += async (s, e) => { @@ -113,7 +140,7 @@ namespace MQTTnet.TestApp.NetFramework .Select(i => CreateMessage()) .ToList(); - if (true) + if (concurrent) { //send concurrent (test for raceconditions) var sendTasks = msgs @@ -150,45 +177,25 @@ namespace MQTTnet.TestApp.NetFramework private static MqttApplicationMessage CreateMessage() { - return new MqttApplicationMessage( - "A/B/C", - Encoding.UTF8.GetBytes("Hello World"), - MqttQualityOfServiceLevel.AtMostOnce, - false - ); + return new MqttApplicationMessage + { + Topic = "A/B/C", + Payload = Encoding.UTF8.GetBytes("Hello World"), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce + }; } private static Task PublishSingleMessage(IMqttClient client, MqttApplicationMessage applicationMessage, ref int count) { Interlocked.Increment(ref count); - return Task.Run(() => - { - return client.PublishAsync(applicationMessage); - }); + return Task.Run(() => client.PublishAsync(applicationMessage)); } - private static async Task RunServerAsync() + private static async Task RunServerAsync(IServiceProvider serviceProvider) { try { - var options = new MqttServerOptions - { - ConnectionValidator = p => - { - if (p.ClientId == "SpecialClient") - { - if (p.Username != "USER" || p.Password != "PASS") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - } - - return MqttConnectReturnCode.ConnectionAccepted; - }, - DefaultCommunicationTimeout = TimeSpan.FromMinutes(10) - }; - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); + var mqttServer = serviceProvider.GetRequiredService(); var msgs = 0; var stopwatch = Stopwatch.StartNew(); mqttServer.ApplicationMessageReceived += (sender, args) => diff --git a/Tests/MQTTnet.TestApp.NetCore/Program.cs b/Tests/MQTTnet.TestApp.NetCore/Program.cs index e89c42a0..f0b28c80 100644 --- a/Tests/MQTTnet.TestApp.NetCore/Program.cs +++ b/Tests/MQTTnet.TestApp.NetCore/Program.cs @@ -1,51 +1,64 @@ using MQTTnet.Core; using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; using MQTTnet.Core.Server; using System; using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace MQTTnet.TestApp.NetCore { public static class Program { - public static void Main(string[] args) + public static void Main() { Console.WriteLine("MQTTnet - TestApp.NetFramework"); Console.WriteLine("1 = Start client"); Console.WriteLine("2 = Start server"); + Console.WriteLine("3 = Start performance test"); + Console.WriteLine("4 = Start managed client"); + var pressedKey = Console.ReadKey(true); - if (pressedKey.Key == ConsoleKey.D1) + if (pressedKey.KeyChar == '1') { - Task.Run(() => RunClientAsync(args)); - Thread.Sleep(Timeout.Infinite); + Task.Run(RunClientAsync); } - else if (pressedKey.Key == ConsoleKey.D2) + else if (pressedKey.KeyChar == '2') { - Task.Run(() => RunServerAsync(args)); - Thread.Sleep(Timeout.Infinite); + Task.Run(ServerTest.RunAsync); } + else if (pressedKey.KeyChar == '3') + { + Task.Run(PerformanceTest.RunAsync); + } + else if (pressedKey.KeyChar == '4') + { + Task.Run(ManagedClientTest.RunAsync); + } + + Thread.Sleep(Timeout.Infinite); } - private static async Task RunClientAsync(string[] arguments) + private static async Task RunClientAsync() { - - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) - { - Console.WriteLine(e.Exception); - } - }; - try { + var services = new ServiceCollection() + .AddMqttServer() + .AddLogging() + .BuildServiceProvider(); + + services.GetService() + .AddConsole(); + var options = new MqttClientWebSocketOptions { Uri = "localhost", @@ -53,7 +66,7 @@ namespace MQTTnet.TestApp.NetCore CleanSession = true }; - var client = new MqttClientFactory().CreateMqttClient(); + var client = services.GetRequiredService(); client.ApplicationMessageReceived += (s, e) => { Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); @@ -106,12 +119,12 @@ namespace MQTTnet.TestApp.NetCore { Console.ReadLine(); - var applicationMessage = new MqttApplicationMessage( - "A/B/C", - Encoding.UTF8.GetBytes("Hello World"), - MqttQualityOfServiceLevel.AtLeastOnce, - false - ); + var applicationMessage = new MqttApplicationMessage + { + Topic = "A/B/C", + Payload = Encoding.UTF8.GetBytes("Hello World"), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce + }; await client.PublishAsync(applicationMessage); } @@ -122,49 +135,59 @@ namespace MQTTnet.TestApp.NetCore } } - private static void RunServerAsync(string[] arguments) + // ReSharper disable once UnusedMember.Local + private static async void WikiCode() { - MqttNetTrace.TraceMessagePublished += (s, e) => { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) - { - Console.WriteLine(e.Exception); - } - }; + var serviceProvider = new ServiceCollection() + .AddMqttServer() + .AddLogging() + .BuildServiceProvider(); - try - { - var options = new MqttServerOptions - { - ConnectionValidator = p => - { - if (p.ClientId == "SpecialClient") - { - if (p.Username != "USER" || p.Password != "PASS") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - } + var client = serviceProvider.GetRequiredService(); - return MqttConnectReturnCode.ConnectionAccepted; - } - }; + var message = new MqttApplicationMessageBuilder() + .WithTopic("MyTopic") + .WithPayload("Hello World") + .WithExactlyOnceQoS() + .WithRetainFlag() + .Build(); - var mqttServer = new MqttServerFactory().CreateMqttServer(options); - mqttServer.StartAsync(); - - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - - mqttServer.StopAsync(); - } - catch (Exception e) - { - Console.WriteLine(e); + await client.PublishAsync(message); } - Console.ReadLine(); + { + var message = new MqttApplicationMessageBuilder() + .WithTopic("/MQTTnet/is/awesome") + .Build(); + } + } + } + + public class RetainedMessageHandler : IMqttServerStorage + { + private const string Filename = "C:\\MQTT\\RetainedMessages.json"; + + public Task SaveRetainedMessagesAsync(IList messages) + { + File.WriteAllText(Filename, JsonConvert.SerializeObject(messages)); + return Task.FromResult(0); + } + + public Task> LoadRetainedMessagesAsync() + { + IList retainedMessages; + if (File.Exists(Filename)) + { + var json = File.ReadAllText(Filename); + retainedMessages = JsonConvert.DeserializeObject>(json); + } + else + { + retainedMessages = new List(); + } + + return Task.FromResult(retainedMessages); } } } diff --git a/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs new file mode 100644 index 00000000..0432a671 --- /dev/null +++ b/Tests/MQTTnet.TestApp.NetCore/ServerTest.cs @@ -0,0 +1,81 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using MQTTnet.Core.Protocol; +using MQTTnet.Core.Server; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace MQTTnet.TestApp.NetCore +{ + public static class ServerTest + { + public static Task RunAsync() + { + try + { + var services = new ServiceCollection() + .AddMqttServer() + .AddLogging(); + + services.Configure(options => + { + options.ConnectionValidator = p => + { + if (p.ClientId == "SpecialClient") + { + if (p.Username != "USER" || p.Password != "PASS") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + } + + return MqttConnectReturnCode.ConnectionAccepted; + }; + + options.Storage = new RetainedMessageHandler(); + + options.ApplicationMessageInterceptor = message => + { + if (MqttTopicFilterComparer.IsMatch(message.Topic, "/myTopic/WithTimestamp/#")) + { + // Replace the payload with the timestamp. But also extending a JSON + // based payload with the timestamp is a suitable use case. + message.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); + } + + return message; + }; + }); + + var serviceProvider = services.BuildServiceProvider(); + + //var certificate = new X509Certificate(@"C:\certs\test\test.cer", ""); + //options.TlsEndpointOptions.Certificate = certificate.Export(X509ContentType.Cert); + //options.ConnectionBacklog = 5; + //options.DefaultEndpointOptions.IsEnabled = true; + //options.TlsEndpointOptions.IsEnabled = false; + + var mqttServer = serviceProvider.GetRequiredService(); + mqttServer.ClientDisconnected += (s, e) => + { + Console.Write("Client disconnected event fired."); + }; + + mqttServer.StartAsync(); + + Console.WriteLine("Press any key to exit."); + Console.ReadLine(); + + mqttServer.StopAsync(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + Console.ReadLine(); + return Task.FromResult(0); + } + } +} diff --git a/Tests/MQTTnet.TestApp.NetCore/messages.bin b/Tests/MQTTnet.TestApp.NetCore/messages.bin new file mode 100644 index 00000000..59505fa8 Binary files /dev/null and b/Tests/MQTTnet.TestApp.NetCore/messages.bin differ diff --git a/Tests/MQTTnet.TestApp.NetFramework/App.config b/Tests/MQTTnet.TestApp.NetFramework/App.config deleted file mode 100644 index 88fa4027..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj b/Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj deleted file mode 100644 index 7d112f5a..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/MQTTnet.TestApp.NetFramework.csproj +++ /dev/null @@ -1,103 +0,0 @@ - - - - - Debug - AnyCPU - {D9D74F33-6943-49B2-B765-7BD589082098} - Exe - MQTTnet.TestApp.NetFramework - MQTTnet.TestApp.NetFramework - v4.5.2 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - - - - - - - - - - - - - - - - - - - - {a480ef90-0eaa-4d9a-b271-47a9c47f6f7d} - MQTTnet.NetFramework - - - {2ecb99e4-72d0-4c23-99ba-93d511d3967d} - MQTTnet.Core - - - - \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetFramework/Program.cs b/Tests/MQTTnet.TestApp.NetFramework/Program.cs deleted file mode 100644 index 606aafaa..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/Program.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Core; -using MQTTnet.Core.Client; -using MQTTnet.Core.Diagnostics; -using MQTTnet.Core.Packets; -using MQTTnet.Core.Protocol; -using MQTTnet.Core.Server; -using MQTTnet.Implementations; - -namespace MQTTnet.TestApp.NetFramework -{ - public static class Program - { - public static void Main(string[] args) - { - Console.WriteLine("MQTTnet - TestApp.NetFramework"); - Console.WriteLine("1 = Start client"); - Console.WriteLine("2 = Start server"); - Console.WriteLine("3 = Start performance test"); - var pressedKey = Console.ReadKey(true); - if (pressedKey.Key == ConsoleKey.D1) - { - Task.Run(() => RunClientAsync(args)); - Thread.Sleep(Timeout.Infinite); - } - else if (pressedKey.Key == ConsoleKey.D2) - { - Task.Run(() => RunServerAsync(args)); - Thread.Sleep(Timeout.Infinite); - } - else if (pressedKey.Key == ConsoleKey.D3) - { - Task.Run(PerformanceTest.RunAsync); - Thread.Sleep(Timeout.Infinite); - } - } - - private static async Task RunClientAsync(string[] arguments) - { - - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) - { - Console.WriteLine(e.Exception); - } - }; - - try - { - var options = new MqttClientWebSocketOptions - { - Uri = "broker.hivemq.com:8000/mqtt" - }; - - ////var options = new MqttClientOptions - ////{ - //// Server = "localhost", - //// ClientId = "XYZ", - //// CleanSession = true - ////}; - - var mqttClient = new MqttClientFactory().CreateMqttClient(); - mqttClient.ApplicationMessageReceived += (s, e) => - { - Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); - Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); - Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); - Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); - Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); - Console.WriteLine(); - }; - - mqttClient.Connected += async (s, e) => - { - Console.WriteLine("### CONNECTED WITH SERVER ###"); - - await mqttClient.SubscribeAsync(new List - { - new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce) - }); - }; - - mqttClient.Disconnected += async (s, e) => - { - Console.WriteLine("### DISCONNECTED FROM SERVER ###"); - await Task.Delay(TimeSpan.FromSeconds(5)); - - try - { - await mqttClient.ConnectAsync(options); - } - catch - { - Console.WriteLine("### RECONNECTING FAILED ###"); - } - }; - - try - { - await mqttClient.ConnectAsync(options); - } - catch (Exception exception) - { - Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception); - } - - Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###"); - - var messageFactory = new MqttApplicationMessageFactory(); - while (true) - { - Console.ReadLine(); - - var applicationMessage = messageFactory.CreateApplicationMessage("myTopic", "Hello World", MqttQualityOfServiceLevel.AtLeastOnce); - await mqttClient.PublishAsync(applicationMessage); - } - } - catch (Exception exception) - { - Console.WriteLine(exception); - } - } - - private static async Task RunServerAsync(string[] arguments) - { - MqttNetTrace.TraceMessagePublished += (s, e) => - { - Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}"); - if (e.Exception != null) - { - Console.WriteLine(e.Exception); - } - }; - - try - { - var options = new MqttServerOptions - { - ConnectionValidator = p => - { - if (p.ClientId == "SpecialClient") - { - if (p.Username != "USER" || p.Password != "PASS") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - } - - return MqttConnectReturnCode.ConnectionAccepted; - } - }; - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); - await mqttServer.StartAsync(); - - Console.WriteLine("Press any key to exit."); - Console.ReadLine(); - - await mqttServer.StopAsync(); - } - catch (Exception e) - { - Console.WriteLine(e); - } - - Console.ReadLine(); - } - - private static async Task WikiCode() - { - // For .NET Framwork & netstandard apps: - MqttTcpChannel.CustomCertificateValidationCallback = (x509Certificate, x509Chain, sslPolicyErrors, mqttClientTcpOptions) => - { - if (mqttClientTcpOptions.Server == "server_with_revoked_cert") - { - return true; - } - - return false; - }; - } - } -} \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs b/Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs deleted file mode 100644 index 5fb54951..00000000 --- a/Tests/MQTTnet.TestApp.NetFramework/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("MQTTnet.TestApp.NetFramework")] -[assembly: AssemblyDescription("")] -[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("7b19b139-2e9d-4f1d-88b4-6180b4cf872a")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs index de5a9f87..3ed0757a 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/App.xaml.cs @@ -1,35 +1,18 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; -using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; namespace MQTTnet.TestApp.UniversalWindows { - /// - /// Provides application-specific behavior to supplement the default Application class. - /// - sealed partial class App : Application + sealed partial class App { - /// - /// Initializes the singleton application object. This is the first line of authored code - /// executed, and as such is the logical equivalent of main() or WinMain(). - /// public App() { - this.InitializeComponent(); - this.Suspending += OnSuspending; + InitializeComponent(); + Suspending += OnSuspending; } /// @@ -39,11 +22,9 @@ namespace MQTTnet.TestApp.UniversalWindows /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs e) { - Frame rootFrame = Window.Current.Content as Frame; - // Do not repeat app initialization when the Window already has content, // just ensure that the window is active - if (rootFrame == null) + if (!(Window.Current.Content is Frame rootFrame)) { // Create a Frame to act as the navigation context and navigate to the first page rootFrame = new Frame(); diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml index b097b50a..e2b0a187 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml @@ -58,7 +58,7 @@ QoS: - 0 (At most once) + 0 (At most once) 1 (At least once) 2 (Exactly once) @@ -73,7 +73,7 @@ QoS: - 0 (At most once) + 0 (At most once) 1 (At least once) 2 (Exactly once) diff --git a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs index 07d5955f..993e227e 100644 --- a/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs +++ b/Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Windows.Security.Cryptography.Certificates; using Windows.UI.Core; using Windows.UI.Xaml; +using Microsoft.Extensions.DependencyInjection; using MQTTnet.Core; using MQTTnet.Core.Client; using MQTTnet.Core.Diagnostics; @@ -29,10 +30,10 @@ namespace MQTTnet.TestApp.UniversalWindows { await Trace.Dispatcher.RunAsync(CoreDispatcherPriority.High, () => { - var text = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{e.Level}] [{e.Source}] [{e.ThreadId}] [{e.Message}]{Environment.NewLine}"; - if (e.Exception != null) + var text = $"[{e.TraceMessage.Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{e.TraceMessage.Level}] [{e.TraceMessage.Source}] [{e.TraceMessage.ThreadId}] [{e.TraceMessage.Message}]{Environment.NewLine}"; + if (e.TraceMessage.Exception != null) { - text += $"{e.Exception}{Environment.NewLine}"; + text += $"{e.TraceMessage.Exception}{Environment.NewLine}"; } Trace.Text += text; @@ -41,7 +42,7 @@ namespace MQTTnet.TestApp.UniversalWindows private async void Connect(object sender, RoutedEventArgs e) { - MqttClientOptions options = null; + BaseMqttClientOptions options = null; if (UseTcp.IsChecked == true) { options = new MqttClientTcpOptions @@ -78,7 +79,7 @@ namespace MQTTnet.TestApp.UniversalWindows await _mqttClient.DisconnectAsync(); } - var factory = new MqttClientFactory(); + var factory = new MqttFactory(); _mqttClient = factory.CreateMqttClient(); await _mqttClient.ConnectAsync(options); } @@ -119,11 +120,12 @@ namespace MQTTnet.TestApp.UniversalWindows payload = Convert.FromBase64String(Payload.Text); } - var message = new MqttApplicationMessage( - Topic.Text, - payload, - qos, - Retain.IsChecked == true); + var message = new MqttApplicationMessageBuilder() + .WithTopic(Topic.Text) + .WithPayload(payload) + .WithQualityOfServiceLevel(qos) + .WithRetainFlag(Retain.IsChecked == true) + .Build(); await _mqttClient.PublishAsync(message); } @@ -195,9 +197,39 @@ namespace MQTTnet.TestApp.UniversalWindows } } + // This code is for the Wiki at GitHub! + // ReSharper disable once UnusedMember.Local private async Task WikiCode() { - var mqttClient = new MqttClientFactory().CreateMqttClient(); + var services = new ServiceCollection() + .AddMqttClient() + .AddMqttServer(options => + { + options.ConnectionValidator = c => + { + if (c.ClientId.Length < 10) + { + return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; + } + + if (c.Username != "mySecretUser") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + + if (c.Password != "mySecretPassword") + { + return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; + } + + return MqttConnectReturnCode.ConnectionAccepted; + }; + }) + .BuildServiceProvider(); + + var factory = new MqttFactory(services); + + var mqttClient = factory.CreateMqttClient(); // ---------------------------------- @@ -236,9 +268,7 @@ namespace MQTTnet.TestApp.UniversalWindows // ---------------------------------- { - var options = new MqttServerOptions(); - - var mqttServer = new MqttServerFactory().CreateMqttServer(options); + var mqttServer = factory.CreateMqttServer(); await mqttServer.StartAsync(); Console.WriteLine("Press any key to exit."); @@ -248,34 +278,7 @@ namespace MQTTnet.TestApp.UniversalWindows } // ---------------------------------- - - { - var options = new MqttServerOptions - { - ConnectionValidator = c => - { - if (c.ClientId.Length < 10) - { - return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected; - } - - if (c.Username != "mySecretUser") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - - if (c.Password != "mySecretPassword") - { - return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword; - } - - return MqttConnectReturnCode.ConnectionAccepted; - } - }; - } - - // ---------------------------------- - + // For UWP apps: MqttTcpChannel.CustomIgnorableServerCertificateErrorsResolver = o => {