diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index c15d5321..42042a0e 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -12,7 +12,7 @@ true MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol. -* [Server] .. +* [Core] Added support for TLS 1.3 (requires .NET Core 3.1+) (thanks to @Dvergatal). Copyright Christian Kratky 2016-2020 MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs index 47d96821..120e6c36 100644 --- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs +++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs @@ -16,14 +16,14 @@ using WebSocket4Net; namespace MQTTnet.Extensions.WebSocket4Net { - public class WebSocket4NetMqttChannel : IMqttChannel + public sealed class WebSocket4NetMqttChannel : IMqttChannel { - private readonly BlockingCollection _receiveBuffer = new BlockingCollection(); + readonly BlockingCollection _receiveBuffer = new BlockingCollection(); - private readonly IMqttClientOptions _clientOptions; - private readonly MqttClientWebSocketOptions _webSocketOptions; + readonly IMqttClientOptions _clientOptions; + readonly MqttClientWebSocketOptions _webSocketOptions; - private WebSocket _webSocket; + WebSocket _webSocket; public WebSocket4NetMqttChannel(IMqttClientOptions clientOptions, MqttClientWebSocketOptions webSocketOptions) { @@ -166,12 +166,12 @@ namespace MQTTnet.Extensions.WebSocket4Net _webSocket = null; } - private void OnError(object sender, ErrorEventArgs e) + static void OnError(object sender, ErrorEventArgs e) { System.Diagnostics.Debug.Write(e.Exception.ToString()); } - private void OnDataReceived(object sender, DataReceivedEventArgs e) + void OnDataReceived(object sender, DataReceivedEventArgs e) { foreach (var @byte in e.Data) { @@ -179,7 +179,7 @@ namespace MQTTnet.Extensions.WebSocket4Net } } - private async Task ConnectInternalAsync(CancellationToken cancellationToken) + async Task ConnectInternalAsync(CancellationToken cancellationToken) { _webSocket.Error += OnError; _webSocket.DataReceived += OnDataReceived; diff --git a/Source/MQTTnet.Extensions.Wrappers.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs b/Source/MQTTnet.Extensions.Wrappers.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs deleted file mode 100644 index ed29be81..00000000 --- a/Source/MQTTnet.Extensions.Wrappers.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs +++ /dev/null @@ -1,134 +0,0 @@ -using MQTTnet.Client; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Security.Authentication; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Adapter; -using MQTTnet.Channel; -using MQTTnet.Diagnostics; -using MQTTnet.Serializer; -using WebSocket4Net; - -namespace MQTTnet.TestApp.NetCore -{ - public class WebSocket4NetMqttClientAdapterFactory : IMqttClientAdapterFactory - { - public IMqttChannelAdapter CreateClientAdapter(IMqttClientOptions options, IMqttNetChildLogger logger) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - - if (!(options.ChannelOptions is MqttClientWebSocketOptions)) - { - throw new NotSupportedException("Only WebSocket connections are supported."); - } - - return new MqttChannelAdapter(new WebSocket4NetMqttChannel(options), new MqttPacketSerializer(), logger); - } - - private class WebSocket4NetMqttChannel : IMqttChannel - { - private readonly BlockingCollection _receiveBuffer = new BlockingCollection(); - - private readonly IMqttClientOptions _clientOptions; - private WebSocket4Net.WebSocket _webSocket; - - public WebSocket4NetMqttChannel(IMqttClientOptions clientOptions) - { - _clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions)); - } - - public string Endpoint { get; } = ""; - - public Task ConnectAsync(CancellationToken cancellationToken) - { - var channelOptions = (MqttClientWebSocketOptions)_clientOptions.ChannelOptions; - - var uri = "ws://" + channelOptions.Uri; - var sslProtocols = SslProtocols.None; - - if (channelOptions.TlsOptions.UseTls) - { - uri = "wss://" + channelOptions.Uri; - sslProtocols = SslProtocols.Tls12; - } - - var subProtocol = channelOptions.SubProtocols.FirstOrDefault() ?? string.Empty; - - _webSocket = new WebSocket4Net.WebSocket(uri, subProtocol, sslProtocols: sslProtocols); - _webSocket.DataReceived += OnDataReceived; - _webSocket.Open(); - SpinWait.SpinUntil(() => _webSocket.State == WebSocketState.Open, _clientOptions.CommunicationTimeout); - - return Task.FromResult(0); - } - - public Task DisconnectAsync() - { - if (_webSocket != null) - { - _webSocket.DataReceived -= OnDataReceived; - _webSocket.Close(); - SpinWait.SpinUntil(() => _webSocket.State == WebSocketState.Closed, _clientOptions.CommunicationTimeout); - } - - _webSocket = null; - - return Task.FromResult(0); - } - - public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var readBytes = 0; - while (count > 0 && !cancellationToken.IsCancellationRequested) - { - byte @byte; - if (readBytes == 0) - { - // Block until at lease one byte was received. - @byte = _receiveBuffer.Take(cancellationToken); - } - else - { - if (!_receiveBuffer.TryTake(out @byte)) - { - return Task.FromResult(readBytes); - } - } - - buffer[offset] = @byte; - offset++; - count--; - readBytes++; - } - - return Task.FromResult(readBytes); - } - - public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - _webSocket.Send(buffer, offset, count); - return Task.FromResult(0); - } - - public void Dispose() - { - if (_webSocket != null) - { - _webSocket.DataReceived -= OnDataReceived; - _webSocket.Dispose(); - } - } - - private void OnDataReceived(object sender, WebSocket4Net.DataReceivedEventArgs e) - { - foreach (var @byte in e.Data) - { - _receiveBuffer.Add(@byte); - } - } - } - } -} diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 8e5c0435..bdb57624 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -212,9 +212,15 @@ namespace MQTTnet.Server.Mqtt // Configure encrypted connections if (_settings.EncryptedTcpEndPoint.Enabled) { +#if NETCOREAPP3_1 || NET5_0 + options + .WithEncryptedEndpoint() + .WithEncryptionSslProtocol(SslProtocols.Tls13); +#else options .WithEncryptedEndpoint() .WithEncryptionSslProtocol(SslProtocols.Tls12); +#endif if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Path)) { diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs index 24b45345..5e2317cf 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs @@ -3,14 +3,16 @@ using MQTTnet.Client.Connecting; namespace MQTTnet.Client.Disconnecting { - public class MqttClientDisconnectedEventArgs : EventArgs + public sealed class MqttClientDisconnectedEventArgs : EventArgs { - public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientAuthenticateResult authenticateResult, MqttClientDisconnectReason reasonCode) + public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientAuthenticateResult authenticateResult, MqttClientDisconnectReason reason) { ClientWasConnected = clientWasConnected; Exception = exception; AuthenticateResult = authenticateResult; - ReasonCode = reasonCode; + Reason = reason; + + ReasonCode = reason; } public bool ClientWasConnected { get; } @@ -19,6 +21,9 @@ namespace MQTTnet.Client.Disconnecting public MqttClientAuthenticateResult AuthenticateResult { get; } - public MqttClientDisconnectReason ReasonCode { get; set; } = MqttClientDisconnectReason.NormalDisconnection; + public MqttClientDisconnectReason Reason { get; set; } + + [Obsolete("Please use 'Reason' instead. This property will be removed in the future!")] + public MqttClientDisconnectReason ReasonCode { get; set; } } } diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs index 6f214eb3..f73ab3b7 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs @@ -19,7 +19,11 @@ namespace MQTTnet.Client.Options public Func CertificateValidationHandler { get; set; } +#if NETCOREAPP3_1 || NET5_0 + public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls13; +#else public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; +#endif #if WINDOWS_UWP public IEnumerable> Certificates { get; set; } diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs index 106aeb14..b5e2c261 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs @@ -26,7 +26,11 @@ namespace MQTTnet.Client.Options public List ApplicationProtocols { get; set; } #endif +#if NETCOREAPP3_1 || NET5_0 + public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls13; +#else public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12; +#endif [Obsolete("This property will be removed soon. Use CertificateValidationHandler instead.")] public Func CertificateValidationCallback { get; set; } diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs index ce224057..7d5706dd 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.Uwp.cs @@ -75,11 +75,23 @@ namespace MQTTnet.Implementations _socket.Control.IgnorableServerCertificateErrors.Add(ignorableChainValidationResult); } +#if NETCOREAPP3_1 || NET5_0 + var socketProtectionLevel = SocketProtectionLevel.Tls13; + if (_options.TlsOptions.SslProtocol == SslProtocols.Tls12) + { + socketProtectionLevel = SocketProtectionLevel.Tls12; + } + else if (_options.TlsOptions.SslProtocol == SslProtocols.Tls11) + { + socketProtectionLevel = SocketProtectionLevel.Tls11; + } +#else var socketProtectionLevel = SocketProtectionLevel.Tls12; if (_options.TlsOptions.SslProtocol == SslProtocols.Tls11) { socketProtectionLevel = SocketProtectionLevel.Tls11; } +#endif else if (_options.TlsOptions.SslProtocol == SslProtocols.Tls) { socketProtectionLevel = SocketProtectionLevel.Tls10; diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index e3959088..534b2433 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -88,7 +88,7 @@ namespace MQTTnet.Implementations ApplicationProtocols = _options.TlsOptions.ApplicationProtocols, ClientCertificates = LoadCertificates(), EnabledSslProtocols = _options.TlsOptions.SslProtocol, - CertificateRevocationCheckMode = _options.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.Online : X509RevocationMode.NoCheck, + CertificateRevocationCheckMode = _options.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : X509RevocationMode.Online, TargetHost = _options.Server }; diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index c265e4d1..bc2d9b86 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -103,6 +104,16 @@ namespace MQTTnet return this; } + public MqttApplicationMessageBuilder WithQualityOfServiceLevel(int qualityOfServiceLevel) + { + if (qualityOfServiceLevel < 0 || qualityOfServiceLevel > 2) + { + throw new ArgumentOutOfRangeException(nameof(qualityOfServiceLevel)); + } + + return WithQualityOfServiceLevel((MqttQualityOfServiceLevel)qualityOfServiceLevel); + } + public MqttApplicationMessageBuilder WithRetainFlag(bool value = true) { _retain = value; diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs index ca6988ab..b0e798cd 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs @@ -68,10 +68,13 @@ namespace MQTTnet.Tests.Mockups public IMqttClient CreateClient() { - var client = _mqttFactory.CreateMqttClient(ClientLogger); - _clients.Add(client); + lock (_clients) + { + var client = _mqttFactory.CreateMqttClient(ClientLogger); + _clients.Add(client); - return new TestClientWrapper(client, TestContext); + return new TestClientWrapper(client, TestContext); + } } public Task StartServerAsync() diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index 0e70a32f..759fc0ba 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -469,11 +469,19 @@ namespace MQTTnet.Tests var client1 = await testEnvironment.ConnectClientAsync(options); await Task.Delay(500); + MqttClientDisconnectReason disconnectReason = MqttClientDisconnectReason.NormalDisconnection; + client1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(c => + { + disconnectReason = c.Reason; + }); + var client2 = await testEnvironment.ConnectClientAsync(options); await Task.Delay(500); Assert.IsFalse(client1.IsConnected); Assert.IsTrue(client2.IsConnected); + + Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason); } } @@ -644,11 +652,11 @@ namespace MQTTnet.Tests { // Clear retained message. await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i2) - .WithPayload(new byte[0]).WithRetainFlag().Build()); + .WithPayload(new byte[0]).WithRetainFlag().WithQualityOfServiceLevel(1).Build()); // Set retained message. await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i2) - .WithPayload("value").WithRetainFlag().Build()); + .WithPayload("value").WithRetainFlag().WithQualityOfServiceLevel(1).Build()); await client.DisconnectAsync(); }