Compare commits

...
Author SHA1 Message Date
Christian 1ba8f74555 Update version 2023-11-04 13:40:28 +01:00
Christian ec637f7946 Handle unobserved tasks exceptions (#1871)
* Access exception so that it will not treated as unhandled

* Refactor code

* Improve unit tests

* Fix null reference exception

* Update ReleaseNotes.md
2023-11-04 13:39:16 +01:00
Christian 2599b3dfdc Update settings for tests project so that it will build on linux (#1860)
* Update settings for tests project so that it will build on linux

* Improve unit tests and samples

* Remove no longer required target frameworks for unit tests

* Update ci.yml
2023-11-01 09:34:18 +01:00
Christian d22caa7b70 Update readme (#1870)
* Update ReleaseNotes.md

* Apply project code style
2023-11-01 09:26:18 +01:00
Rido 01c90badf9 Support custom CA chain validation (#1851)
* Add CaFile based on SslStream CustomTrust

* change to cert collection

* do not require to disable revocation checks
2023-11-01 09:22:13 +01:00
Diego2098andChristian e4f605fbd5 Update MqttServer.cs (#1858)
* Update MqttServer.cs

clientID is null => RetainedMessageChangedEventArgs ctor throw new ArgumentNullException(nameof(clientId)

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-11-01 08:29:19 +01:00
Christian 6fdee9e0db Fix wrong TLS usage (#1833)
* Fix wrong usage of TLS options

* Fix public broker tests

* Update ReleaseNotes.md
2023-09-06 20:08:35 +02:00
Christian cae7206733 Fix NullReferenceException when calling Ping (#1834)
* Check connection status before doing ping

* Fix broken reconnect sample

* Update ReleaseNotes.md
2023-09-06 20:08:13 +02:00
Christian e2e7aacc09 Update version and release notes 2023-09-06 20:07:45 +02:00
25 changed files with 430 additions and 255 deletions
+3 -11
View File
@@ -1,11 +1,3 @@
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar). * [Server] Fixed not working _UpdateRetainedMessageAsync_ public api (#1858, thanks to @kimdiego2098).
* [Client] Added hot reload of client certificates (#1781). * [Client] Added support for custom CA chain validation (#1851, thanks to @rido-min).
* [Client] Added several new option builders and aligned usage (#1781, BREAKING CHANGE!). * [Client] Fixed handling of unobserved tasks exceptions (#1871).
* [Client] Added support for _RemoteCertificateValidationCallback_ for .NET 4.5.2, 4.6.1 and 4.8 (#1806, thanks to @troky).
* [Client] Fixed wrong logging of obsolete feature when connection was not successful (#1801, thanks to @ramonsmits).
* [Client] Fixed _NullReferenceException_ when performing several actions when not connected (#1800, thanks to @ramonsmits).
* [RpcClient] Added support for passing custom parameters to topic generation context (#1798, thanks to @Temppus).
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
* [Server] Expired sessions will no longer be used when a client connects (#1756).
* [Server] Fixed an issue in connection handling for ASP.NET connections (#1819, thanks to @CZEMacLeod).
+2 -2
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.3.0.${{github.run_number}}" VERSION: "4.3.2.${{github.run_number}}"
jobs: jobs:
build: build:
@@ -54,7 +54,7 @@ jobs:
uses: darenm/Setup-VSTest@v1 uses: darenm/Setup-VSTest@v1
- name: Core Tests - name: Core Tests
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net7.0\MQTTnet.Tests.dll
- name: ASP.NET Tests - name: ASP.NET Tests
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
+1
View File
@@ -240,6 +240,7 @@ See the LICENSE file in the project root for more information.</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=catched/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
@@ -7,6 +7,7 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net; using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -430,6 +431,59 @@ public static class Client_Connection_Samples
} }
} }
}); });
Console.WriteLine("Press <Enter> to exit");
Console.ReadLine();
} }
} }
public static async Task ConnectTls_WithCaFile()
{
var mqttFactory = new MqttFactory();
X509Certificate2Collection caChain = new X509Certificate2Collection();
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions(new MqttClientTlsOptionsBuilder()
.WithTrustChain(caChain)
.Build())
.Build();
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
}
}
const string mosquitto_org = @"
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE-----
";
} }
+3 -1
View File
@@ -184,7 +184,7 @@ public static class Client_Subscribe_Samples
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged * This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide * This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
*/ */
mqttClient.ApplicationMessageReceivedAsync += async ea => mqttClient.ApplicationMessageReceivedAsync += ea =>
{ {
ea.AutoAcknowledge = false; ea.AutoAcknowledge = false;
@@ -201,6 +201,8 @@ public static class Client_Subscribe_Samples
} }
_ = Task.Run(ProcessAsync, shutdownToken); _ = Task.Run(ProcessAsync, shutdownToken);
return Task.CompletedTask;
}; };
} }
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@@ -47,13 +47,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
var uri = _webSocketOptions.Uri; var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{ {
if (_webSocketOptions.TlsOptions?.UseTls == false) if (_webSocketOptions.TlsOptions?.UseTls == true)
{ {
uri = "ws://" + uri; uri = "wss://" + uri;
} }
else else
{ {
uri = "wss://" + uri; uri = "ws://" + uri;
} }
} }
+13 -9
View File
@@ -25,7 +25,9 @@ namespace MQTTnet.TestApp
UseTls = true, UseTls = true,
SslProtocol = SslProtocols.Tls13, SslProtocol = SslProtocols.Tls13,
// Don't use this in production code. This handler simply allows any invalid certificate to work. // Don't use this in production code. This handler simply allows any invalid certificate to work.
CertificateValidationHandler = w => true AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
}; };
#endif #endif
// Also defining TLS12 for servers that don't seem no to support TLS13. // Also defining TLS12 for servers that don't seem no to support TLS13.
@@ -34,7 +36,9 @@ namespace MQTTnet.TestApp
UseTls = true, UseTls = true,
SslProtocol = SslProtocols.Tls12, SslProtocol = SslProtocols.Tls12,
// Don't use this in production code. This handler simply allows any invalid certificate to work. // Don't use this in production code. This handler simply allows any invalid certificate to work.
CertificateValidationHandler = w => true AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
}; };
// mqtt.eclipseprojects.io // mqtt.eclipseprojects.io
@@ -97,10 +101,10 @@ namespace MQTTnet.TestApp
"test.mosquitto.org WS TLS12", "test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build()); new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
// await ExecuteTestAsync( await ExecuteTestAsync(
// "test.mosquitto.org WS TLS12 (WebSocket4Net)", "test.mosquitto.org WS TLS12 (WebSocket4Net)",
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(), new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
// true); true);
// broker.emqx.io // broker.emqx.io
await ExecuteTestAsync( await ExecuteTestAsync(
@@ -150,7 +154,6 @@ namespace MQTTnet.TestApp
true); true);
// mqtt.swifitch.cz: Does not seem to operate any more // mqtt.swifitch.cz: Does not seem to operate any more
// cloudmqtt.com: Cannot test because it does not offer a free plan any more. // cloudmqtt.com: Cannot test because it does not offer a free plan any more.
Write("Finished.", ConsoleColor.White); Write("Finished.", ConsoleColor.White);
@@ -197,9 +200,10 @@ namespace MQTTnet.TestApp
Write("[OK]\n", ConsoleColor.Green); Write("[OK]\n", ConsoleColor.Green);
} }
catch (Exception e) catch (Exception exception)
{ {
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red); Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red);
Write(exception + Environment.NewLine, ConsoleColor.Red);
} }
} }
@@ -22,99 +22,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
[TestClass] [TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
{ {
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod] [TestMethod]
public async Task Connect_To_Invalid_Server() public async Task Connect_To_Invalid_Server()
{ {
@@ -181,6 +88,38 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
} }
} }
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod] [TestMethod]
public async Task ManagedClients_Will_Message_Send() public async Task ManagedClients_Will_Message_Send()
{ {
@@ -224,6 +163,67 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
} }
} }
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod] [TestMethod]
public async Task Start_Stop() public async Task Start_Stop()
{ {
@@ -375,7 +375,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>(); var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient()); var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
managedClient.ApplicationMessageReceivedAsync += e => managedClient.ApplicationMessageReceivedAsync += e =>
{ {
@@ -403,7 +403,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
// Make sure that it gets received after subscribing again. // Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await LongTestDelay(); await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count); Assert.AreEqual(2, receivedManagedMessages.Count);
} }
} }
@@ -421,7 +421,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient(); var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>( new byte[] { 1 }), Retain = true }); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
var subscribeTime = DateTime.UtcNow; var subscribeTime = DateTime.UtcNow;
@@ -167,6 +167,38 @@ namespace MQTTnet.Tests.Clients.MqttClient
} }
} }
[TestMethod]
public async Task No_Unobserved_Exception()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var client = testEnvironment.CreateClient();
var options = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithTimeout(TimeSpan.FromSeconds(2)).Build();
try
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(0.5)))
{
await client.ConnectAsync(options, timeout.Token);
}
}
catch (OperationCanceledException)
{
}
client.Dispose();
// These delays and GC calls are required in order to make calling the finalizer reproducible.
GC.Collect();
GC.WaitForPendingFinalizers();
await LongTestDelay();
await LongTestDelay();
await LongTestDelay();
}
}
[TestMethod] [TestMethod]
public async Task Return_Non_Success() public async Task Return_Non_Success()
{ {
+2 -2
View File
@@ -52,7 +52,7 @@ namespace MQTTnet.Tests.Extensions
var paramValue = "123"; var paramValue = "123";
var parameters = new Dictionary<string, object> var parameters = new Dictionary<string, object>
{ {
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" }, { TestParametersTopicGenerationStrategy.ExpectedParamName, "123" }
}; };
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Extensions
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{ {
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
} }
} }
} }
+2 -1
View File
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net48</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
+1 -1
View File
@@ -273,7 +273,7 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
CollectionAssert.AreEqual(applicationMessage.Payload, receivedMessage.Payload); CollectionAssert.AreEqual(applicationMessage.PayloadSegment.ToArray(), receivedMessage.PayloadSegment.ToArray());
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
} }
} }
+87 -39
View File
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -25,9 +24,9 @@ namespace MQTTnet.Tests.Mockups
public sealed class TestEnvironment : IDisposable public sealed class TestEnvironment : IDisposable
{ {
readonly List<string> _clientErrors = new List<string>(); readonly List<string> _clientErrors = new List<string>();
readonly ConcurrentBag<IMqttClient> _clients = new ConcurrentBag<IMqttClient>(); readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>(); readonly List<Exception> _exceptions = new List<Exception>();
readonly ConcurrentBag<ILowLevelMqttClient> _lowLevelClients = new ConcurrentBag<ILowLevelMqttClient>(); readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly MqttProtocolVersion _protocolVersion; readonly MqttProtocolVersion _protocolVersion;
readonly List<string> _serverErrors = new List<string>(); readonly List<string> _serverErrors = new List<string>();
@@ -40,6 +39,8 @@ namespace MQTTnet.Tests.Mockups
_protocolVersion = protocolVersion; _protocolVersion = protocolVersion;
TestContext = testContext; TestContext = testContext;
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
ServerLogger.LogMessagePublished += (s, e) => ServerLogger.LogMessagePublished += (s, e) =>
{ {
if (Debugger.IsAttached) if (Debugger.IsAttached)
@@ -216,8 +217,9 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient() public IMqttClient CreateClient()
{ {
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance; var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
var client = Factory.CreateMqttClient(logger); var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e => client.ConnectingAsync += e =>
{ {
if (TestContext != null) if (TestContext != null)
@@ -232,9 +234,12 @@ namespace MQTTnet.Tests.Mockups
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
_clients.Add(client); lock (_clients)
{
_clients.Add(client);
}
return client; return client;
} }
@@ -245,13 +250,20 @@ namespace MQTTnet.Tests.Mockups
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder() public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{ {
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid()); return Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(_protocolVersion)
.WithTcpServer("127.0.0.1", ServerPort)
.WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
} }
public ILowLevelMqttClient CreateLowLevelClient() public ILowLevelMqttClient CreateLowLevelClient()
{ {
var client = Factory.CreateLowLevelMqttClient(ClientLogger); var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
lock (_lowLevelClients)
{
_lowLevelClients.Add(client);
}
return client; return client;
} }
@@ -287,45 +299,68 @@ namespace MQTTnet.Tests.Mockups
public void Dispose() public void Dispose()
{ {
foreach (var mqttClient in _clients) try
{ {
lock (_clients)
{
foreach (var mqttClient in _clients)
{
try
{
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already disconnected the client.
}
finally
{
mqttClient?.Dispose();
}
}
_clients.Clear();
}
lock (_lowLevelClients)
{
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
}
_lowLevelClients.Clear();
}
try try
{ {
//mqttClient.DisconnectAsync().GetAwaiter().GetResult(); Server?.StopAsync().GetAwaiter().GetResult();
} }
catch catch
{ {
// This can happen when the test already disconnected the client. // This can happen when the test already stopped the server.
} }
finally finally
{ {
mqttClient?.Dispose(); Server?.Dispose();
} }
}
foreach (var lowLevelMqttClient in _lowLevelClients) Server = null;
{
lowLevelMqttClient.Dispose();
}
try ThrowIfLogErrors();
{
Server?.StopAsync().GetAwaiter().GetResult(); GC.Collect();
} GC.WaitForFullGCComplete();
catch GC.WaitForPendingFinalizers();
{
// This can happen when the test already stopped the server. if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
}
} }
finally finally
{ {
Server?.Dispose(); TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException;
}
ThrowIfLogErrors();
if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
} }
} }
@@ -342,7 +377,7 @@ namespace MQTTnet.Tests.Mockups
var options = optionsBuilder.Build(); var options = optionsBuilder.Build();
var server = CreateServer(options); var server = CreateServer(options);
await server.StartAsync(); await server.StartAsync().ConfigureAwait(false);
// The OS has chosen the port to we have to properly expose it to the tests. // The OS has chosen the port to we have to properly expose it to the tests.
ServerPort = options.DefaultEndpointOptions.Port; ServerPort = options.DefaultEndpointOptions.Port;
@@ -370,13 +405,16 @@ namespace MQTTnet.Tests.Mockups
public void ThrowIfLogErrors() public void ThrowIfLogErrors()
{ {
lock (_serverErrors) if (!IgnoreServerLogErrors)
{ {
if (!IgnoreServerLogErrors && _serverErrors.Count > 0) lock (_serverErrors)
{ {
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)})."; if (_serverErrors.Count > 0)
Console.WriteLine(message); {
throw new Exception(message); var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
Console.WriteLine(message);
throw new Exception(message);
}
} }
} }
@@ -396,10 +434,20 @@ namespace MQTTnet.Tests.Mockups
public void TrackException(Exception exception) public void TrackException(Exception exception)
{ {
if (exception == null)
{
return;
}
lock (_exceptions) lock (_exceptions)
{ {
_exceptions.Add(exception); _exceptions.Add(exception);
} }
} }
void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
TrackException(e.Exception);
}
} }
} }
@@ -1,3 +1,5 @@
#pragma warning disable CS0618 // Type or member is obsolete
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -8,7 +10,7 @@ using System.Threading.Tasks;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public class MqttApplicationMessageTest public sealed class MqttApplicationMessageTest
{ {
[TestMethod] [TestMethod]
public void PayloadSegment() public void PayloadSegment()
@@ -17,32 +19,27 @@ namespace MQTTnet.Tests
Assert.AreEqual(0, message.Payload.Length); Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count); Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload)); Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 }; message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length); Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count); Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload)); Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 }; message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length); Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count); Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload)); Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }); message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length); Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count); Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload)); Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1); message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length); Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count); Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload)); Assert.IsFalse(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsFalse(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
} }
} }
} }
@@ -1,4 +1,4 @@
#if !(NET452 || NET461) #if !(NET452 || NET461 || NET48)
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
@@ -149,18 +149,14 @@ namespace MQTTnet.Tests.Server
} }
} }
class ClientTestHarness : IDisposable sealed class ClientTestHarness : IDisposable
{ {
readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider(); readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider();
IMqttClient _client; IMqttClient _client;
public string ClientId => _client.Options.ClientId; public string ClientId => _client.Options.ClientId;
public void ClearServerCerts()
{
_hotSwapClient.ClearServerCerts();
}
public Task Connect() public Task Connect()
{ {
return Run_Client_Connection(); return Run_Client_Connection();
@@ -169,6 +165,7 @@ namespace MQTTnet.Tests.Server
public void Dispose() public void Dispose()
{ {
_client.Dispose(); _client.Dispose();
_hotSwapClient.Dispose();
} }
public X509Certificate2 GetCurrentClientCert() public X509Certificate2 GetCurrentClientCert()
@@ -189,6 +186,8 @@ namespace MQTTnet.Tests.Server
public void WaitForConnectOrFail(TimeSpan timeout) public void WaitForConnectOrFail(TimeSpan timeout)
{ {
Thread.Sleep(100);
if (!_client.IsConnected) if (!_client.IsConnected)
{ {
_client.ReconnectAsync().Wait(timeout); _client.ReconnectAsync().Wait(timeout);
@@ -210,12 +209,12 @@ namespace MQTTnet.Tests.Server
Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail"); Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail");
} }
public void WaitForDisconnect(TimeSpan timeout) void WaitForDisconnect(TimeSpan timeout)
{ {
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout) while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout)
{ {
Thread.Sleep(5); Thread.Sleep(100);
} }
} }
@@ -252,22 +251,17 @@ namespace MQTTnet.Tests.Server
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout) while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout)
{ {
Thread.Sleep(5); Thread.Sleep(100);
} }
} }
} }
class ServerTestHarness : IDisposable sealed class ServerTestHarness : IDisposable
{ {
readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider(); readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider();
MqttServer _server; MqttServer _server;
public void ClearClientCerts()
{
_hotSwapServer.ClearClientCerts();
}
public void Dispose() public void Dispose()
{ {
if (_server != null) if (_server != null)
@@ -276,15 +270,12 @@ namespace MQTTnet.Tests.Server
_server.Dispose(); _server.Dispose();
} }
if (_hotSwapServer != null) _hotSwapServer?.Dispose();
{
_hotSwapServer.Dispose();
}
} }
public async Task ForceDisconnectAsync(ClientTestHarness client) public Task ForceDisconnectAsync(ClientTestHarness client)
{ {
await _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError); return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError);
} }
public X509Certificate2 GetCurrentServerCert() public X509Certificate2 GetCurrentServerCert()
@@ -302,7 +293,7 @@ namespace MQTTnet.Tests.Server
_hotSwapServer.InstallNewClientCert(serverCert); _hotSwapServer.InstallNewClientCert(serverCert);
} }
public async Task StartServer() public Task StartServer()
{ {
var mqttFactory = new MqttFactory(); var mqttFactory = new MqttFactory();
@@ -312,24 +303,33 @@ namespace MQTTnet.Tests.Server
.Build(); .Build();
mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true; mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true;
_server = mqttFactory.CreateMqttServer(mqttServerOptions); _server = mqttFactory.CreateMqttServer(mqttServerOptions);
await _server.StartAsync(); return _server.StartAsync();
} }
} }
class HotSwappableClientCertProvider : IMqttClientCertificatesProvider class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable
{ {
X509Certificate2Collection _certificates; X509Certificate2Collection _certificates;
ConcurrentBag<X509Certificate2> ServerCerts = new ConcurrentBag<X509Certificate2>(); ConcurrentBag<X509Certificate2> _serverCerts = new ConcurrentBag<X509Certificate2>();
public HotSwappableClientCertProvider() public HotSwappableClientCertProvider()
{ {
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2")); _certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
} }
public void ClearServerCerts() public void Dispose()
{ {
ServerCerts = new ConcurrentBag<X509Certificate2>(); if (_certificates != null)
{
foreach (var certs in _certificates)
{
#if !NET452
certs.Dispose();
#endif
}
}
} }
public X509CertificateCollection GetCertificates() public X509CertificateCollection GetCertificates()
@@ -339,18 +339,17 @@ namespace MQTTnet.Tests.Server
public void HotSwapCert() public void HotSwapCert()
{ {
var newCert = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2")); _certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
var oldCerts = Interlocked.Exchange(ref _certificates, newCert);
} }
public void InstallNewServerCert(X509Certificate2 serverCert) public void InstallNewServerCert(X509Certificate2 serverCert)
{ {
ServerCerts.Add(serverCert); _serverCerts.Add(serverCert);
} }
public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext) public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext)
{ {
var serverCerts = ServerCerts.ToArray(); var serverCerts = _serverCerts.ToArray();
var providedCert = certContext.Certificate.GetRawCertData(); var providedCert = certContext.Certificate.GetRawCertData();
for (int i = 0, n = serverCerts.Length; i < n; i++) for (int i = 0, n = serverCerts.Length; i < n; i++)
@@ -365,36 +364,18 @@ namespace MQTTnet.Tests.Server
return false; return false;
} }
void Dispose()
{
if (_certificates != null)
{
foreach (var certs in _certificates)
{
#if !NET452
certs.Dispose();
#endif
}
}
}
} }
class HotSwappableServerCertProvider : ICertificateProvider, IDisposable sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable
{ {
readonly ConcurrentBag<X509Certificate2> _clientCerts = new ConcurrentBag<X509Certificate2>();
X509Certificate2 _certificate; X509Certificate2 _certificate;
ConcurrentBag<X509Certificate2> ClientCerts = new ConcurrentBag<X509Certificate2>();
public HotSwappableServerCertProvider() public HotSwappableServerCertProvider()
{ {
_certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1"); _certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
} }
public void ClearClientCerts()
{
ClientCerts = new ConcurrentBag<X509Certificate2>();
}
public void Dispose() public void Dispose()
{ {
#if !NET452 #if !NET452
@@ -418,12 +399,12 @@ namespace MQTTnet.Tests.Server
public void InstallNewClientCert(X509Certificate2 certificate) public void InstallNewClientCert(X509Certificate2 certificate)
{ {
ClientCerts.Add(certificate); _clientCerts.Add(certificate);
} }
public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{ {
var serverCerts = ClientCerts.ToArray(); var serverCerts = _clientCerts.ToArray();
var providedCert = certificate.GetRawCertData(); var providedCert = certificate.GetRawCertData();
for (int i = 0, n = serverCerts.Length; i < n; i++) for (int i = 0, n = serverCerts.Length; i < n; i++)
+23 -7
View File
@@ -26,9 +26,7 @@ namespace MQTTnet.Adapter
readonly IMqttChannel _channel; readonly IMqttChannel _channel;
readonly byte[] _fixedHeaderBuffer = new byte[2]; readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly byte[] _singleByteBuffer = new byte[1]; readonly byte[] _singleByteBuffer = new byte[1];
readonly AsyncLock _syncRoot = new AsyncLock(); readonly AsyncLock _syncRoot = new AsyncLock();
Statistics _statistics; // mutable struct, don't make readonly! Statistics _statistics; // mutable struct, don't make readonly!
@@ -47,6 +45,8 @@ namespace MQTTnet.Adapter
_logger = logger.WithSource(nameof(MqttChannelAdapter)); _logger = logger.WithSource(nameof(MqttChannelAdapter));
} }
public bool AllowPacketFragmentation { get; set; } = true;
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived); public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
public long BytesSent => Volatile.Read(ref _statistics._bytesSent); public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
@@ -63,8 +63,6 @@ namespace MQTTnet.Adapter
public MqttPacketInspector PacketInspector { get; set; } public MqttPacketInspector PacketInspector { get; set; }
public bool AllowPacketFragmentation { get; set; } = true;
public async Task ConnectAsync(CancellationToken cancellationToken) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -78,12 +76,30 @@ namespace MQTTnet.Adapter
* block forever. Even a cancellation token is not supported properly. * block forever. Even a cancellation token is not supported properly.
*/ */
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>(); var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null))) using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{ {
var connectTask = Task.Run(
async () =>
{
try
{
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// If the timeout is already reached the exception is no longer of interest and
// must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException.
if (!timeout.Task.IsCompleted)
{
throw;
}
}
},
CancellationToken.None);
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false); await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted) if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{ {
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken); throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
@@ -204,7 +220,7 @@ namespace MQTTnet.Adapter
PacketInspector?.BeginSendPacket(packetBuffer); PacketInspector?.BeginSendPacket(packetBuffer);
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation) if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
{ {
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
+17 -7
View File
@@ -238,6 +238,11 @@ namespace MQTTnet.Client
public async Task PingAsync(CancellationToken cancellationToken = default) public async Task PingAsync(CancellationToken cancellationToken = default)
{ {
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
ThrowIfNotConnected();
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false); await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
@@ -441,7 +446,7 @@ namespace MQTTnet.Client
if (receivedPacket is MqttConnAckPacket connAckPacket) if (receivedPacket is MqttConnAckPacket connAckPacket)
{ {
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion); result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
} }
else if (receivedPacket is MqttAuthPacket) else if (receivedPacket is MqttAuthPacket)
{ {
@@ -513,7 +518,7 @@ namespace MQTTnet.Client
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
{ {
_logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions); _logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions);
await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false); await channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
_logger.Verbose("Connection with server established"); _logger.Verbose("Connection with server established");
_publishPacketReceiverQueue?.Dispose(); _publishPacketReceiverQueue?.Dispose();
@@ -703,6 +708,9 @@ namespace MQTTnet.Client
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false); await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
} }
} }
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
@@ -782,7 +790,7 @@ namespace MQTTnet.Client
{ {
try try
{ {
_logger.Verbose("Start receiving packets."); _logger.Verbose("Start receiving packets");
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
@@ -820,20 +828,22 @@ namespace MQTTnet.Client
} }
else if (exception is MqttCommunicationException) else if (exception is MqttCommunicationException)
{ {
_logger.Warning(exception, "Communication error while receiving packets."); _logger.Warning(exception, "Communication error while receiving packets");
} }
else else
{ {
_logger.Error(exception, "Error while receiving packets."); _logger.Error(exception, "Error while receiving packets");
} }
_packetDispatcher.FailAll(exception); // The packet dispatcher is set to null when the client is being disposed so it may
// already being gone!
_packetDispatcher?.FailAll(exception);
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false); await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
} }
finally finally
{ {
_logger.Verbose("Stopped receiving packets."); _logger.Verbose("Stopped receiving packets");
} }
} }
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#pragma warning disable CS0612 // Type or member is obsolete
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -32,7 +34,12 @@ namespace MQTTnet.Client
throw new InvalidOperationException("A channel must be set."); throw new InvalidOperationException("A channel must be set.");
} }
var tlsOptions = _tlsOptions; // The user can specify the TCP options with already configured TLS options
// or start with TLS settings not knowing which transport will be used (depending
// on the order of called methods from the builder).
// The builder prefers the explicitly set TLS options!
var tlsOptions = _tlsOptions ?? _tcpOptions?.TlsOptions;
if (_tlsParameters != null) if (_tlsParameters != null)
{ {
if (_tlsParameters?.UseTls == true) if (_tlsParameters?.UseTls == true)
@@ -50,8 +50,13 @@ namespace MQTTnet.Client
#if NET48 || NETCOREAPP3_1_OR_GREATER #if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else #else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif #endif
#if NET7_0_OR_GREATER
public X509Certificate2Collection TrustChain { get; set; }
#endif
} }
} }
@@ -131,6 +131,15 @@ namespace MQTTnet.Client
_tlsOptions.EncryptionPolicy = encryptionPolicy; _tlsOptions.EncryptionPolicy = encryptionPolicy;
return this; return this;
} }
#endif
#if NET7_0_OR_GREATER
public MqttClientTlsOptionsBuilder WithTrustChain(X509Certificate2Collection chain)
{
_tlsOptions.TrustChain = chain;
return this;
}
#endif #endif
} }
} }
@@ -115,14 +115,31 @@ namespace MQTTnet.Implementations
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols, ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
ClientCertificates = LoadCertificates(), ClientCertificates = LoadCertificates(),
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol, EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
CertificateRevocationCheckMode = CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode, ? X509RevocationMode.NoCheck
: _tcpOptions.TlsOptions.RevocationMode,
TargetHost = targetHost, TargetHost = targetHost,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy, CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy,
EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy, EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy,
AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation
}; };
#if NET7_0_OR_GREATER
if (_tcpOptions.TlsOptions.TrustChain?.Count > 0)
{
sslOptions.CertificateChainPolicy = new X509ChainPolicy
{
TrustMode = X509ChainTrustMode.CustomRootTrust,
VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown,
RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors
? X509RevocationMode.NoCheck
: _tcpOptions.TlsOptions.RevocationMode
};
sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain);
}
#endif
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
#else #else
await sslStream.AuthenticateAsClientAsync( await sslStream.AuthenticateAsClientAsync(
@@ -46,13 +46,13 @@ namespace MQTTnet.Implementations
var uri = _options.Uri; var uri = _options.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{ {
if (_options.TlsOptions?.UseTls == false) if (_options.TlsOptions?.UseTls == true)
{ {
uri = "ws://" + uri; uri = "wss://" + uri;
} }
else else
{ {
uri = "wss://" + uri; uri = "ws://" + uri;
} }
} }
@@ -19,7 +19,6 @@ namespace MQTTnet.LowLevelClient
readonly IMqttClientAdapterFactory _clientAdapterFactory; readonly IMqttClientAdapterFactory _clientAdapterFactory;
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>(); readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly IMqttNetLogger _rootLogger; readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter; IMqttChannelAdapter _adapter;
+2 -2
View File
@@ -362,7 +362,7 @@ namespace MQTTnet.Server
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotStarted(); ThrowIfNotStarted();
return _retainedMessagesManager?.UpdateMessage(null, retainedMessage); return _retainedMessagesManager?.UpdateMessage(string.Empty, retainedMessage);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -405,4 +405,4 @@ namespace MQTTnet.Server
} }
} }
} }
} }