mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
30
Commits
v4.2.1.781
...
v4.3.2.930
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae84aa43d2 | ||
|
|
b431a5d3fe | ||
|
|
b9683c2943 | ||
|
|
1ba8f74555 | ||
|
|
ec637f7946 | ||
|
|
2599b3dfdc | ||
|
|
d22caa7b70 | ||
|
|
01c90badf9 | ||
|
|
e4f605fbd5 | ||
|
|
6fdee9e0db | ||
|
|
cae7206733 | ||
|
|
e2e7aacc09 | ||
|
|
0d3dd96f2a | ||
|
|
e0660ba887 | ||
|
|
5965ded488 | ||
|
|
fc31c3077c | ||
|
|
e604345e65 | ||
|
|
111a0d69cf | ||
|
|
1a08ec17ba | ||
|
|
c537d290c3 | ||
|
|
2180f59725 | ||
|
|
b1dc8607af | ||
|
|
101898695e | ||
|
|
9295626503 | ||
|
|
18614d4e81 | ||
|
|
560e80455f | ||
|
|
a97e88be29 | ||
|
|
3b5667c882 | ||
|
|
0790e54794 | ||
|
|
94f9fac2bd |
@@ -1,13 +1,7 @@
|
|||||||
* [Core] Add validation of maximum string lengths (#1718).
|
* [Client] Added support for custom CA chain validation (#1851, thanks to @rido-min).
|
||||||
* [Core] Added .NET 4.8 builds (#1729).
|
* [Client] Added support for custom CA chain validation (#1851, thanks to @rido-min).
|
||||||
* [Core] Exposed more details of DISCONNECT packet in log (#1729).
|
* [Client] Fixed handling of unobserved tasks exceptions (#1871).
|
||||||
* [Core] Fixed _ArgumentNullException_ in _ConvertPayloadToString_ (#1745, thanks to @ramonsmits).
|
* [Client] Fixed not specified ReasonCode when using _SendExtendedAuthenticationExchangeDataAsync_ (#1882, thanks to @rido-min).
|
||||||
* [Client] Added overloads for setting packet payload and will payload (#1720).
|
* [Server] Fixed not working _UpdateRetainedMessageAsync_ public api (#1858, thanks to @kimdiego2098).
|
||||||
* [Client] The proper connect result is now exposed in the _Disconnected_ event when authentication fails (#1139).
|
* [Server] Added support for custom DISCONNECT packets when stopping the server or disconnect a client (BREAKING CHANGE!, #1846).
|
||||||
* [Client] Exposed _UseDefaultCredentials_ and more for Web Socket options and proxy options (#1734, thanks to @impworks).
|
* [Server] Added new property to stop the server from accepting new connections even if it is running (#1846).
|
||||||
* [Client] Exposed more TLS options (#1729).
|
|
||||||
* [Client] Fixed wrong return code conversion (#1729).
|
|
||||||
* [Client] Added an option to avoid throwing an exception when the server returned a proper non success (but valid) response (#1681).
|
|
||||||
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
|
|
||||||
* [Server] Fixed exceptions when clients are connecting and disconnecting very fast while accessing the client status for connection validation (#1742).
|
|
||||||
* [Server] Exposed more properties in _ClientConnectedEventArgs_ (#1738).
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.2.1.${{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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -70,32 +71,6 @@ public static class Client_Connection_Samples
|
|||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Connect_With_Amazon_AWS()
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
|
|
||||||
*
|
|
||||||
* The broker requires special settings which are set here.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
|
||||||
{
|
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("amazon.web.services.broker")
|
|
||||||
// Disabling packet fragmentation is very important!
|
|
||||||
.WithoutPacketFragmentation()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
|
||||||
|
|
||||||
Console.WriteLine("The MQTT client is connected.");
|
|
||||||
|
|
||||||
await mqttClient.DisconnectAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Connect_Client_Timeout()
|
public static async Task Connect_Client_Timeout()
|
||||||
{
|
{
|
||||||
@@ -161,15 +136,15 @@ public static class Client_Connection_Samples
|
|||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
// certificates. This should not be used in live environments.
|
// certificates. This should not be used in live environments.
|
||||||
o.CertificateValidationHandler = _ => true;
|
o.WithCertificateValidationHandler(_ => true);
|
||||||
|
|
||||||
// The default value is determined by the OS. Set manually to force version.
|
// The default value is determined by the OS. Set manually to force version.
|
||||||
o.SslProtocol = SslProtocols.Tls12;
|
o.WithSslProtocols(SslProtocols.Tls12);
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -196,7 +171,7 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||||
|
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -218,7 +193,7 @@ public static class Client_Connection_Samples
|
|||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||||
|
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -241,13 +216,11 @@ public static class Client_Connection_Samples
|
|||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o => o.WithCertificateValidationHandler(
|
||||||
{
|
|
||||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
// certificates. This should not be used in live environments.
|
// certificates. This should not be used in live environments.
|
||||||
o.CertificateValidationHandler = _ => true;
|
_ => true))
|
||||||
})
|
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// In MQTTv5 the response contains much more information.
|
// In MQTTv5 the response contains much more information.
|
||||||
@@ -262,6 +235,31 @@ public static class Client_Connection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Connect_With_Amazon_AWS()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
|
||||||
|
*
|
||||||
|
* The broker requires special settings which are set here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
|
||||||
|
// Disabling packet fragmentation is very important!
|
||||||
|
.WithoutPacketFragmentation()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
|
||||||
|
await mqttClient.DisconnectAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Disconnect_Clean()
|
public static async Task Disconnect_Clean()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -317,18 +315,19 @@ public static class Client_Connection_Samples
|
|||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
o.CertificateValidationHandler = eventArgs =>
|
o.WithCertificateValidationHandler(
|
||||||
{
|
eventArgs =>
|
||||||
eventArgs.Certificate.Subject.DumpToConsole();
|
{
|
||||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
eventArgs.Certificate.Subject.DumpToConsole();
|
||||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||||
return true;
|
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||||
};
|
return true;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -432,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-----
|
||||||
|
";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
// ReSharper disable UnusedType.Global
|
// ReSharper disable UnusedType.Global
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
// ReSharper disable UnusedMember.Local
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -176,4 +177,62 @@ public static class Client_Subscribe_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += ea =>
|
||||||
|
{
|
||||||
|
ea.AutoAcknowledge = false;
|
||||||
|
|
||||||
|
async Task ProcessAsync()
|
||||||
|
{
|
||||||
|
// DO YOUR WORK HERE!
|
||||||
|
await Task.Delay(1000, shutdownToken);
|
||||||
|
await ea.AcknowledgeAsync(shutdownToken);
|
||||||
|
// WARNING: If process failures are not transient the message will be retried on every restart of the client
|
||||||
|
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
|
||||||
|
// the broker know processing failed
|
||||||
|
//
|
||||||
|
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample shows how to achieve concurrent processing, with:
|
||||||
|
* - a maximum concurrency limit based on Environment.ProcessorCount
|
||||||
|
*/
|
||||||
|
|
||||||
|
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
|
||||||
|
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += async ea =>
|
||||||
|
{
|
||||||
|
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
async Task ProcessAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// DO YOUR WORK HERE!
|
||||||
|
await Task.Delay(1000, shutdownToken);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
concurrent.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(ProcessAsync, shutdownToken);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||||
|
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||||
|
|
||||||
|
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||||
Console.WriteLine("Retained messages loaded.");
|
Console.WriteLine("Retained messages loaded.");
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
@@ -56,7 +61,9 @@ public static class Server_Retained_Messages_Samples
|
|||||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||||
// of retained messages must be provided in the loaded event.
|
// of retained messages must be provided in the loaded event.
|
||||||
|
|
||||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||||
|
|
||||||
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||||
await File.WriteAllBytesAsync(storePath, buffer);
|
await File.WriteAllBytesAsync(storePath, buffer);
|
||||||
Console.WriteLine("Retained messages saved.");
|
Console.WriteLine("Retained messages saved.");
|
||||||
}
|
}
|
||||||
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
|
|||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class MqttRetainedMessageModel
|
||||||
|
{
|
||||||
|
public string? ContentType { get; set; }
|
||||||
|
public byte[]? CorrelationData { get; set; }
|
||||||
|
public byte[]? Payload { get; set; }
|
||||||
|
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
|
||||||
|
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||||
|
public string? ResponseTopic { get; set; }
|
||||||
|
public string? Topic { get; set; }
|
||||||
|
public List<MqttUserProperty>? UserProperties { get; set; }
|
||||||
|
|
||||||
|
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||||
|
{
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttRetainedMessageModel
|
||||||
|
{
|
||||||
|
Topic = message.Topic,
|
||||||
|
|
||||||
|
// Create a copy of the buffer from the payload segment because
|
||||||
|
// it cannot be serialized and deserialized with the JSON serializer.
|
||||||
|
Payload = message.PayloadSegment.ToArray(),
|
||||||
|
UserProperties = message.UserProperties,
|
||||||
|
ResponseTopic = message.ResponseTopic,
|
||||||
|
CorrelationData = message.CorrelationData,
|
||||||
|
ContentType = message.ContentType,
|
||||||
|
PayloadFormatIndicator = message.PayloadFormatIndicator,
|
||||||
|
QualityOfServiceLevel = message.QualityOfServiceLevel
|
||||||
|
|
||||||
|
// Other properties like "Retain" are not if interest in the storage.
|
||||||
|
// That's why a custom model makes sense.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessage ToApplicationMessage()
|
||||||
|
{
|
||||||
|
return new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = Topic,
|
||||||
|
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
|
||||||
|
PayloadFormatIndicator = PayloadFormatIndicator,
|
||||||
|
ResponseTopic = ResponseTopic,
|
||||||
|
CorrelationData = CorrelationData,
|
||||||
|
ContentType = ContentType,
|
||||||
|
UserProperties = UserProperties,
|
||||||
|
QualityOfServiceLevel = QualityOfServiceLevel,
|
||||||
|
Dup = false,
|
||||||
|
Retain = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -33,8 +33,11 @@ namespace MQTTnet.AspNetCore
|
|||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||||
|
|
||||||
_input = connection.Transport.Input;
|
if (!(_connection is TcpConnection tcp) || tcp.IsConnected)
|
||||||
_output = connection.Transport.Output;
|
{
|
||||||
|
_input = connection.Transport.Input;
|
||||||
|
_output = connection.Transport.Output;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long BytesReceived { get; private set; }
|
public long BytesReceived { get; private set; }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -14,20 +15,27 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
public sealed class MqttHostedServer : MqttServer, IHostedService
|
public sealed class MqttHostedServer : MqttServer, IHostedService
|
||||||
{
|
{
|
||||||
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
readonly MqttFactory _mqttFactory;
|
||||||
: base(options, adapters, logger)
|
|
||||||
|
public MqttHostedServer(MqttFactory mqttFactory, MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
|
||||||
|
options,
|
||||||
|
adapters,
|
||||||
|
logger)
|
||||||
{
|
{
|
||||||
|
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_ = StartAsync();
|
// The yield makes sure that the hosted service is considered up and running.
|
||||||
return Task.CompletedTask;
|
await Task.Yield();
|
||||||
|
|
||||||
|
_ = StartAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return StopAsync();
|
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
@@ -14,86 +14,73 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
|
||||||
{
|
|
||||||
if (serviceCollection is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(serviceCollection));
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceCollection.AddMqttConnectionHandler();
|
|
||||||
serviceCollection.AddHostedMqttServer(configure);
|
|
||||||
|
|
||||||
return serviceCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
services.AddSingleton(options);
|
services.AddSingleton(options);
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
|
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||||
{
|
{
|
||||||
services.AddSingleton(s =>
|
if (services == null)
|
||||||
{
|
{
|
||||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
throw new ArgumentNullException(nameof(services));
|
||||||
configure?.Invoke(serverOptionsBuilder);
|
}
|
||||||
return serverOptionsBuilder.Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
return services;
|
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||||
|
configure.Invoke(serverOptionsBuilder);
|
||||||
|
var options = serverOptionsBuilder.Build();
|
||||||
|
|
||||||
|
return AddHostedMqttServer(services, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddHostedMqttServer(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// The user may have these services already registered.
|
||||||
|
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
|
||||||
|
services.TryAddSingleton(new MqttFactory());
|
||||||
|
|
||||||
|
services.AddSingleton<MqttHostedServer>();
|
||||||
|
services.AddHostedService<MqttHostedServer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||||
{
|
{
|
||||||
services.AddSingleton(s =>
|
if (services == null)
|
||||||
{
|
{
|
||||||
var builder = new AspNetMqttServerOptionsBuilder(s);
|
throw new ArgumentNullException(nameof(services));
|
||||||
configure(builder);
|
}
|
||||||
return builder.Build();
|
|
||||||
});
|
services.AddSingleton(
|
||||||
|
s =>
|
||||||
|
{
|
||||||
|
var builder = new AspNetMqttServerOptionsBuilder(s);
|
||||||
|
configure(builder);
|
||||||
|
return builder.Build();
|
||||||
|
});
|
||||||
|
|
||||||
services.AddHostedMqttServer();
|
services.AddHostedMqttServer();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
var logger = new MqttNetEventLogger();
|
|
||||||
|
|
||||||
services.AddSingleton<IMqttNetLogger>(logger);
|
|
||||||
services.AddSingleton<MqttHostedServer>();
|
|
||||||
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
|
|
||||||
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<MqttWebSocketServerAdapter>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton<MqttTcpServerAdapter>();
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<MqttConnectionHandler>();
|
services.AddSingleton<MqttConnectionHandler>();
|
||||||
@@ -101,5 +88,44 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
if (services == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddSingleton(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||||
|
{
|
||||||
|
if (serviceCollection is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(serviceCollection));
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceCollection.AddMqttConnectionHandler();
|
||||||
|
serviceCollection.AddHostedMqttServer(configure);
|
||||||
|
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MqttTcpServerAdapter>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddMqttWebSocketServerAdapter(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<MqttWebSocketServerAdapter>();
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttWebSocketServerAdapter>());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -7,8 +8,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public interface IMqttRpcClient : IDisposable
|
public interface IMqttRpcClient : IDisposable
|
||||||
{
|
{
|
||||||
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
|
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
|
||||||
|
|
||||||
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default);
|
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -42,13 +43,13 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
_waitingCalls.Clear();
|
_waitingCalls.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
|
||||||
{
|
{
|
||||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false);
|
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException exception)
|
catch (OperationCanceledException exception)
|
||||||
{
|
{
|
||||||
@@ -62,14 +63,14 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (methodName == null)
|
if (methodName == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(methodName));
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
|
||||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||||
|
|
||||||
var requestTopic = topicNames.RequestTopic;
|
var requestTopic = topicNames.RequestTopic;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// 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.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -11,13 +12,13 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public static class MqttRpcClientExtensions
|
public static class MqttRpcClientExtensions
|
||||||
{
|
{
|
||||||
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
|
||||||
{
|
{
|
||||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
|
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
|
||||||
|
|
||||||
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel);
|
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// 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.Generic;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -10,9 +11,11 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public sealed class TopicGenerationContext
|
public sealed class TopicGenerationContext
|
||||||
{
|
{
|
||||||
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
|
||||||
|
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||||
|
Parameters = parameters;
|
||||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
@@ -20,6 +23,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
|
|
||||||
public string MethodName { get; }
|
public string MethodName { get; }
|
||||||
|
|
||||||
|
public IDictionary<string, object> Parameters { get; }
|
||||||
|
|
||||||
public IMqttClient MqttClient { get; }
|
public IMqttClient MqttClient { get; }
|
||||||
|
|
||||||
public MqttRpcClientOptions Options { get; }
|
public MqttRpcClientOptions Options { get; }
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,18 +92,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
var webSocketVersion = WebSocketVersion.None;
|
var webSocketVersion = WebSocketVersion.None;
|
||||||
var receiveBufferSize = 0;
|
var receiveBufferSize = 0;
|
||||||
|
|
||||||
var certificates = new X509CertificateCollection();
|
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
|
||||||
if (_webSocketOptions.TlsOptions?.Certificates != null)
|
|
||||||
{
|
|
||||||
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
|
||||||
{
|
|
||||||
#if WINDOWS_UWP
|
|
||||||
certificates.Add(new X509Certificate(certificate));
|
|
||||||
#else
|
|
||||||
certificates.Add(certificate);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
|
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,21 +20,25 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
// TLS13 is only available in Net5.0
|
// TLS13 is only available in Net5.0
|
||||||
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls13 = new MqttClientTlsOptions
|
||||||
{
|
{
|
||||||
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.
|
||||||
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls12 = new MqttClientTlsOptions
|
||||||
{
|
{
|
||||||
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
|
||||||
@@ -44,16 +48,16 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"mqtt.eclipseprojects.io WS",
|
"mqtt.eclipseprojects.io WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
|
||||||
true);
|
true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -68,12 +72,12 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"test.mosquitto.org TCP TLS12",
|
"test.mosquitto.org TCP TLS12",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
@@ -81,26 +85,26 @@ namespace MQTTnet.TestApp
|
|||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
||||||
.WithCredentials("rw", "readwrite")
|
.WithCredentials("rw", "readwrite")
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311)
|
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||||
.WithTls(unsafeTls12)
|
.WithTlsOptions(unsafeTls12)
|
||||||
.Build());
|
.Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"test.mosquitto.org WS",
|
"test.mosquitto.org WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"test.mosquitto.org WS (WebSocket4Net)",
|
"test.mosquitto.org WS (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"test.mosquitto.org WS TLS12",
|
"test.mosquitto.org WS TLS12",
|
||||||
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());
|
||||||
|
|
||||||
// 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(
|
||||||
@@ -109,30 +113,30 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.emqx.io TCP TLS12",
|
"broker.emqx.io TCP TLS12",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.emqx.io WS",
|
"broker.emqx.io WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.emqx.io WS (WebSocket4Net)",
|
"broker.emqx.io WS (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.emqx.io WS TLS12",
|
"broker.emqx.io WS TLS12",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
// broker.hivemq.com
|
// broker.hivemq.com
|
||||||
@@ -142,15 +146,14 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.hivemq.com WS",
|
"broker.hivemq.com WS",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync(
|
await ExecuteTestAsync(
|
||||||
"broker.hivemq.com WS (WebSocket4Net)",
|
"broker.hivemq.com WS (WebSocket4Net)",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using MQTTnet.Exceptions;
|
|||||||
using MQTTnet.LowLevelClient;
|
using MQTTnet.LowLevelClient;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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", Payload = 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;
|
||||||
|
|
||||||
@@ -454,7 +454,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
//wait a bit for the subscription to become established
|
//wait a bit for the subscription to become established
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
|
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
|
||||||
|
|
||||||
var messages = await SetupReceivingOfMessages(managedClient, 1);
|
var messages = await SetupReceivingOfMessages(managedClient, 1);
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
@@ -196,5 +228,27 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Throw_Proper_Exception_When_Not_Connected()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MqttCommunicationException exception)
|
||||||
|
{
|
||||||
|
if (exception.Message == "The client is not connected.")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@@ -296,7 +297,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
|
|
||||||
Assert.IsNotNull(receivedMessage);
|
Assert.IsNotNull(receivedMessage);
|
||||||
Assert.AreEqual("A", receivedMessage.Topic);
|
Assert.AreEqual("A", receivedMessage.Topic);
|
||||||
Assert.AreEqual(null, receivedMessage.Payload);
|
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +508,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
|
|
||||||
client2.ApplicationMessageReceivedAsync += e =>
|
client2.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
|
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public class Extension_Tests
|
|
||||||
{
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfter()
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
|
|
||||||
TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterWithResult()
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(
|
|
||||||
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
|
|
||||||
TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterWithInnerException()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
|
|
||||||
{
|
|
||||||
var iis = new int[0];
|
|
||||||
iis[1] = 0;
|
|
||||||
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
|
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Assert.IsTrue(e is IndexOutOfRangeException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterWithInnerExceptionWithResult()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
|
|
||||||
{
|
|
||||||
var iis = new int[0];
|
|
||||||
iis[1] = 0;
|
|
||||||
return iis[0];
|
|
||||||
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
|
|
||||||
|
|
||||||
Assert.Fail();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Assert.IsTrue(e is IndexOutOfRangeException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TimeoutAfterMemoryUsage()
|
|
||||||
{
|
|
||||||
var initialMemory = GC.GetTotalMemory(true);
|
|
||||||
|
|
||||||
var tasks = Enumerable.Range(0, 100000)
|
|
||||||
.Select(i =>
|
|
||||||
{
|
|
||||||
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
|
|
||||||
TimeSpan.FromMinutes(1), CancellationToken.None);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void AssertIsLess(long bound, long actual)
|
|
||||||
{
|
|
||||||
if (bound < actual)
|
|
||||||
{
|
|
||||||
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// 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.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -45,6 +46,34 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Execute_Success_Parameters_Propagated_Correctly()
|
||||||
|
{
|
||||||
|
var paramValue = "123";
|
||||||
|
var parameters = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" }
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
||||||
|
await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}");
|
||||||
|
|
||||||
|
responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
||||||
|
|
||||||
|
using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder()
|
||||||
|
.WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()))
|
||||||
|
{
|
||||||
|
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters);
|
||||||
|
|
||||||
|
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0()
|
public Task Execute_Success_With_QoS_0()
|
||||||
{
|
{
|
||||||
@@ -135,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,5 +251,32 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||||
|
{
|
||||||
|
internal const string ExpectedParamName = "test_param_name";
|
||||||
|
|
||||||
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
|
{
|
||||||
|
if (context.Parameters == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Parameters dictionary expected to be not null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present");
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}";
|
||||||
|
var responseTopic = requestTopic + "/response";
|
||||||
|
|
||||||
|
return new MqttRpcTopicPair
|
||||||
|
{
|
||||||
|
RequestTopic = requestTopic,
|
||||||
|
ResponseTopic = responseTopic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ namespace MQTTnet.Tests.Extensions
|
|||||||
|
|
||||||
using (var client = factory.CreateMqttClient())
|
using (var client = factory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
var options = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("ws://a.b/mqtt")).WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||||
await client.ConnectAsync(options).ConfigureAwait(false);
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.MQTTv5
|
namespace MQTTnet.Tests.MQTTv5
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
{
|
{
|
||||||
readonly List<string> _clientErrors = new List<string>();
|
readonly List<string> _clientErrors = new List<string>();
|
||||||
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
||||||
|
|
||||||
readonly List<Exception> _exceptions = new List<Exception>();
|
readonly List<Exception> _exceptions = new List<Exception>();
|
||||||
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
||||||
readonly MqttProtocolVersion _protocolVersion;
|
readonly MqttProtocolVersion _protocolVersion;
|
||||||
@@ -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)
|
||||||
@@ -65,9 +66,12 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||||
{
|
{
|
||||||
lock (_clientErrors)
|
if (!IgnoreClientLogErrors)
|
||||||
{
|
{
|
||||||
_clientErrors.Add(e.LogMessage.ToString());
|
lock (_clientErrors)
|
||||||
|
{
|
||||||
|
_clientErrors.Add(e.LogMessage.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -212,30 +216,31 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public IMqttClient CreateClient()
|
public IMqttClient CreateClient()
|
||||||
{
|
{
|
||||||
|
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
|
||||||
|
|
||||||
|
var client = Factory.CreateMqttClient(logger);
|
||||||
|
|
||||||
|
client.ConnectingAsync += e =>
|
||||||
|
{
|
||||||
|
if (TestContext != null)
|
||||||
|
{
|
||||||
|
var clientOptions = e.ClientOptions;
|
||||||
|
var existingClientId = clientOptions.ClientId;
|
||||||
|
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
|
||||||
|
{
|
||||||
|
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
{
|
{
|
||||||
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
|
|
||||||
|
|
||||||
var client = Factory.CreateMqttClient(logger);
|
|
||||||
_clients.Add(client);
|
_clients.Add(client);
|
||||||
|
|
||||||
client.ConnectingAsync += e =>
|
|
||||||
{
|
|
||||||
if (TestContext != null)
|
|
||||||
{
|
|
||||||
var clientOptions = e.ClientOptions;
|
|
||||||
var existingClientId = clientOptions.ClientId;
|
|
||||||
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
|
|
||||||
{
|
|
||||||
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptions CreateDefaultClientOptions()
|
public MqttClientOptions CreateDefaultClientOptions()
|
||||||
@@ -245,18 +250,22 @@ 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()
|
||||||
{
|
{
|
||||||
lock (_clients)
|
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||||
{
|
|
||||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
|
||||||
_lowLevelClients.Add(client);
|
|
||||||
|
|
||||||
return client;
|
lock (_lowLevelClients)
|
||||||
|
{
|
||||||
|
_lowLevelClients.Add(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttServer CreateServer(MqttServerOptions options)
|
public MqttServer CreateServer(MqttServerOptions options)
|
||||||
@@ -290,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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,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;
|
||||||
@@ -373,33 +405,49 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_clientErrors)
|
if (!IgnoreClientLogErrors)
|
||||||
{
|
{
|
||||||
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
|
lock (_clientErrors)
|
||||||
{
|
{
|
||||||
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
if (_clientErrors.Count > 0)
|
||||||
Console.WriteLine(message);
|
{
|
||||||
throw new Exception(message);
|
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
||||||
|
Console.WriteLine(message);
|
||||||
|
throw new Exception(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -11,7 +12,7 @@ using MQTTnet.Protocol;
|
|||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class MqttApplicationMessageBuilder_Tests
|
public sealed class MqttApplicationMessageBuilder_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CreateApplicationMessage_TopicOnly()
|
public void CreateApplicationMessage_TopicOnly()
|
||||||
@@ -29,7 +30,7 @@ namespace MQTTnet.Tests
|
|||||||
Assert.AreEqual("xyz", message.Topic);
|
Assert.AreEqual("xyz", message.Topic);
|
||||||
Assert.IsFalse(message.Retain);
|
Assert.IsFalse(message.Retain);
|
||||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
|
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -41,7 +42,7 @@ namespace MQTTnet.Tests
|
|||||||
Assert.AreEqual("123", message.Topic);
|
Assert.AreEqual("123", message.Topic);
|
||||||
Assert.IsFalse(message.Retain);
|
Assert.IsFalse(message.Retain);
|
||||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
|
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var server = await testEnvironment.StartServer();
|
var server = await testEnvironment.StartServer();
|
||||||
server.InterceptingPublishAsync += e =>
|
server.InterceptingPublishAsync += e =>
|
||||||
{
|
{
|
||||||
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
|
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("extended"));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var isIntercepted = false;
|
var isIntercepted = false;
|
||||||
c2.ApplicationMessageReceivedAsync += e =>
|
c2.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
|
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()), StringComparison.Ordinal) == 0;
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -425,7 +425,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "/test/1",
|
Topic = "/test/1",
|
||||||
Payload = Encoding.UTF8.GetBytes("true"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("true")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@@ -598,8 +598,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
// c
|
// c
|
||||||
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
var flow = string.Join(string.Empty, events);
|
var flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("c", flow);
|
Assert.AreEqual("c", flow);
|
||||||
|
|
||||||
@@ -607,10 +606,8 @@ namespace MQTTnet.Tests.Server
|
|||||||
// Connect client with same client ID. Should disconnect existing client.
|
// Connect client with same client ID. Should disconnect existing client.
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
|
|
||||||
Assert.AreEqual("cdc", flow);
|
Assert.AreEqual("cdc", flow);
|
||||||
|
|
||||||
c2.ApplicationMessageReceivedAsync += e =>
|
c2.ApplicationMessageReceivedAsync += e =>
|
||||||
@@ -628,8 +625,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
// r
|
// r
|
||||||
await c2.PublishStringAsync("topic");
|
await c2.PublishStringAsync("topic");
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("cdcr", flow);
|
Assert.AreEqual("cdcr", flow);
|
||||||
|
|
||||||
@@ -639,16 +635,17 @@ namespace MQTTnet.Tests.Server
|
|||||||
await c1.TryDisconnectAsync();
|
await c1.TryDisconnectAsync();
|
||||||
Assert.AreEqual(false, c1.IsConnected);
|
Assert.AreEqual(false, c1.IsConnected);
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
// d
|
// d
|
||||||
Assert.AreEqual(true, c2.IsConnected);
|
Assert.AreEqual(true, c2.IsConnected);
|
||||||
await c2.DisconnectAsync();
|
await c2.DisconnectAsync();
|
||||||
|
|
||||||
await Task.Delay(500);
|
await LongTestDelay();
|
||||||
|
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("cdcrd", flow);
|
Assert.AreEqual("cdcrd", flow);
|
||||||
}
|
}
|
||||||
@@ -783,7 +780,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var client1 = await testEnvironment.ConnectClient();
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
client1.ApplicationMessageReceivedAsync += e =>
|
client1.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
receivedBody = e.ApplicationMessage.Payload;
|
receivedBody = e.ApplicationMessage.PayloadSegment.ToArray();
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -852,11 +849,11 @@ namespace MQTTnet.Tests.Server
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
await Task.Delay(100);
|
// Stopping the server should disconnect the connection with the client
|
||||||
|
// which, it turn, will fire the disconnected event at the client.
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
await Task.Delay(100);
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, disconnectCalled);
|
Assert.AreEqual(1, disconnectCalled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,426 @@
|
|||||||
|
#if !(NET452 || NET461 || NET48)
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
// missing certificate builder api means tests won't work for older frameworks
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public sealed class HotSwapCerts_Tests
|
||||||
|
{
|
||||||
|
static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ClientCertChangeWithoutServerUpdateFailsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.HotSwapClientCert();
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.WaitForConnectToFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ClientCertChangeWithServerUpdateAcceptsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.HotSwapClientCert();
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ServerCertChangeWithClientCertUpdateAllowsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
server.HotSwapServerCert();
|
||||||
|
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ServerCertChangeWithoutClientCertUpdateFailsReconnect()
|
||||||
|
{
|
||||||
|
using (var server = new ServerTestHarness())
|
||||||
|
using (var client01 = new ClientTestHarness())
|
||||||
|
{
|
||||||
|
server.InstallNewClientCert(client01.GetCurrentClientCert());
|
||||||
|
client01.InstallNewServerCert(server.GetCurrentServerCert());
|
||||||
|
|
||||||
|
await server.StartServer();
|
||||||
|
await client01.Connect();
|
||||||
|
|
||||||
|
client01.WaitForConnectOrFail(DefaultTimeout);
|
||||||
|
server.HotSwapServerCert();
|
||||||
|
|
||||||
|
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
|
||||||
|
client01.WaitForDisconnectOrFail(DefaultTimeout);
|
||||||
|
|
||||||
|
client01.WaitForConnectToFail(DefaultTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||||
|
{
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ClientTestHarness : IDisposable
|
||||||
|
{
|
||||||
|
readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider();
|
||||||
|
|
||||||
|
IMqttClient _client;
|
||||||
|
|
||||||
|
public string ClientId => _client.Options.ClientId;
|
||||||
|
|
||||||
|
public Task Connect()
|
||||||
|
{
|
||||||
|
return Run_Client_Connection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
_hotSwapClient.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 GetCurrentClientCert()
|
||||||
|
{
|
||||||
|
var result = _hotSwapClient.GetCertificates()[0];
|
||||||
|
return new X509Certificate2(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapClientCert()
|
||||||
|
{
|
||||||
|
_hotSwapClient.HotSwapCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewServerCert(X509Certificate2 serverCert)
|
||||||
|
{
|
||||||
|
_hotSwapClient.InstallNewServerCert(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForConnectOrFail(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
|
||||||
|
if (!_client.IsConnected)
|
||||||
|
{
|
||||||
|
_client.ReconnectAsync().Wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForConnect(timeout);
|
||||||
|
|
||||||
|
Assert.IsNotNull(_client, "Client was never initialized");
|
||||||
|
Assert.IsTrue(_client.IsConnected, $"Client connection failed after {timeout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForConnectToFail(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
Assert.IsFalse(_client.IsConnected, "Client should be disconnected before waiting for connect.");
|
||||||
|
|
||||||
|
WaitForConnect(timeout);
|
||||||
|
|
||||||
|
Assert.IsNotNull(_client, "Client was never initialized");
|
||||||
|
Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForDisconnect(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForDisconnectOrFail(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
WaitForDisconnect(timeout);
|
||||||
|
|
||||||
|
Assert.IsNotNull(_client, "Client was never initialized");
|
||||||
|
Assert.IsFalse(_client.IsConnected, $"Client connection should have disconnected after {timeout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task Run_Client_Connection()
|
||||||
|
{
|
||||||
|
var optionsBuilder = new MqttClientOptionsBuilder()
|
||||||
|
.WithTlsOptions(
|
||||||
|
o => o.WithClientCertificatesProvider(_hotSwapClient)
|
||||||
|
.WithCertificateValidationHandler(_hotSwapClient.OnCertificateValidation)
|
||||||
|
.WithSslProtocols(SslProtocols.Tls12))
|
||||||
|
.WithTcpServer("localhost")
|
||||||
|
.WithCleanSession()
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V500);
|
||||||
|
|
||||||
|
var mqttClientOptions = optionsBuilder.Build();
|
||||||
|
|
||||||
|
var factory = new MqttFactory();
|
||||||
|
var mqttClient = factory.CreateMqttClient();
|
||||||
|
_client = mqttClient;
|
||||||
|
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForConnect(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var timer = Stopwatch.StartNew();
|
||||||
|
while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ServerTestHarness : IDisposable
|
||||||
|
{
|
||||||
|
readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider();
|
||||||
|
|
||||||
|
MqttServer _server;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_server != null)
|
||||||
|
{
|
||||||
|
_server.StopAsync().Wait();
|
||||||
|
_server.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_hotSwapServer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ForceDisconnectAsync(ClientTestHarness client)
|
||||||
|
{
|
||||||
|
return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 GetCurrentServerCert()
|
||||||
|
{
|
||||||
|
return _hotSwapServer.GetCertificate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapServerCert()
|
||||||
|
{
|
||||||
|
_hotSwapServer.HotSwapCert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewClientCert(X509Certificate2 serverCert)
|
||||||
|
{
|
||||||
|
_hotSwapServer.InstallNewClientCert(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartServer()
|
||||||
|
{
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(_hotSwapServer)
|
||||||
|
.WithRemoteCertificateValidationCallback(_hotSwapServer.RemoteCertificateValidationCallback)
|
||||||
|
.WithEncryptedEndpoint()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true;
|
||||||
|
|
||||||
|
_server = mqttFactory.CreateMqttServer(mqttServerOptions);
|
||||||
|
return _server.StartAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable
|
||||||
|
{
|
||||||
|
X509Certificate2Collection _certificates;
|
||||||
|
ConcurrentBag<X509Certificate2> _serverCerts = new ConcurrentBag<X509Certificate2>();
|
||||||
|
|
||||||
|
public HotSwappableClientCertProvider()
|
||||||
|
{
|
||||||
|
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_certificates != null)
|
||||||
|
{
|
||||||
|
foreach (var certs in _certificates)
|
||||||
|
{
|
||||||
|
#if !NET452
|
||||||
|
certs.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509CertificateCollection GetCertificates()
|
||||||
|
{
|
||||||
|
return new X509Certificate2Collection(_certificates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapCert()
|
||||||
|
{
|
||||||
|
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewServerCert(X509Certificate2 serverCert)
|
||||||
|
{
|
||||||
|
_serverCerts.Add(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext)
|
||||||
|
{
|
||||||
|
var serverCerts = _serverCerts.ToArray();
|
||||||
|
|
||||||
|
var providedCert = certContext.Certificate.GetRawCertData();
|
||||||
|
for (int i = 0, n = serverCerts.Length; i < n; i++)
|
||||||
|
{
|
||||||
|
var currentcert = serverCerts[i];
|
||||||
|
|
||||||
|
if (currentcert.RawData.SequenceEqual(providedCert))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable
|
||||||
|
{
|
||||||
|
readonly ConcurrentBag<X509Certificate2> _clientCerts = new ConcurrentBag<X509Certificate2>();
|
||||||
|
X509Certificate2 _certificate;
|
||||||
|
|
||||||
|
public HotSwappableServerCertProvider()
|
||||||
|
{
|
||||||
|
_certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
#if !NET452
|
||||||
|
_certificate.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 GetCertificate()
|
||||||
|
{
|
||||||
|
return _certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HotSwapCert()
|
||||||
|
{
|
||||||
|
var newCert = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||||
|
var oldCert = Interlocked.Exchange(ref _certificate, newCert);
|
||||||
|
#if !NET452
|
||||||
|
oldCert.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallNewClientCert(X509Certificate2 certificate)
|
||||||
|
{
|
||||||
|
_clientCerts.Add(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
|
{
|
||||||
|
var serverCerts = _clientCerts.ToArray();
|
||||||
|
|
||||||
|
var providedCert = certificate.GetRawCertData();
|
||||||
|
for (int i = 0, n = serverCerts.Length; i < n; i++)
|
||||||
|
{
|
||||||
|
var currentCert = serverCerts[i];
|
||||||
|
|
||||||
|
if (currentCert.RawData.SequenceEqual(providedCert))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttRetainedMessageManager_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttRetainedMessageManager_GetUndefinedTopic()
|
||||||
|
{
|
||||||
|
var logger = new Mockups.TestLogger();
|
||||||
|
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
|
||||||
|
var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger);
|
||||||
|
var task = retainedMessagesManager.GetMessage("undefined");
|
||||||
|
Assert.IsNotNull(task, "Task should not be null");
|
||||||
|
var result = await task;
|
||||||
|
Assert.IsNull(result, "Null result expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -221,7 +221,10 @@ namespace MQTTnet.Tests.Server
|
|||||||
var eventContainer = new MqttServerEventContainer();
|
var eventContainer = new MqttServerEventContainer();
|
||||||
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
||||||
|
|
||||||
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
var session = new MqttSession(new MqttConnectPacket
|
||||||
|
{
|
||||||
|
ClientId = ""
|
||||||
|
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
||||||
|
|
||||||
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using MQTTnet.Client;
|
|||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,6 +70,28 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Do_Not_Use_Expired_Session()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3));
|
||||||
|
|
||||||
|
// Kill the client connection and ensure that the session will stay there but is expired after 3 seconds!
|
||||||
|
c1.Dispose();
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(6));
|
||||||
|
|
||||||
|
c1 = testEnvironment.CreateClient();
|
||||||
|
var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build();
|
||||||
|
|
||||||
|
var connectResult = await c1.ConnectAsync(options);
|
||||||
|
Assert.AreEqual(false, connectResult.IsSessionPresent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Fire_Deleted_Event()
|
public async Task Fire_Deleted_Event()
|
||||||
{
|
{
|
||||||
@@ -121,7 +143,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(MqttProtocolVersion.V310)]
|
[DataRow(MqttProtocolVersion.V310)]
|
||||||
[DataRow(MqttProtocolVersion.V311)]
|
[DataRow(MqttProtocolVersion.V311)]
|
||||||
@@ -134,16 +156,12 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
|
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
|
||||||
|
|
||||||
var hasReceive = false;
|
var hasReceive = false;
|
||||||
|
|
||||||
void OnReceive()
|
void OnReceive()
|
||||||
{
|
{
|
||||||
if (!hasReceive)
|
hasReceive = true;
|
||||||
{
|
|
||||||
hasReceive = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to connect 50 clients at the same time.
|
// Try to connect 50 clients at the same time.
|
||||||
@@ -152,7 +170,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, connectedClients.Count);
|
Assert.AreEqual(1, connectedClients.Count);
|
||||||
|
|
||||||
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
|
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
|
||||||
@@ -164,7 +182,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, hasReceive);
|
Assert.AreEqual(true, hasReceive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||||
@@ -265,7 +283,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
server.InterceptingPublishAsync += e =>
|
server.InterceptingPublishAsync += e =>
|
||||||
{
|
{
|
||||||
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
|
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -355,8 +373,8 @@ namespace MQTTnet.Tests.Server
|
|||||||
onReceive();
|
onReceive();
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||||
{
|
{
|
||||||
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
|
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var eventContainer = new MqttServerEventContainer();
|
var eventContainer = new MqttServerEventContainer();
|
||||||
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
||||||
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
||||||
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
_clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
||||||
|
|
||||||
List<string> topicsToSubscribe;
|
List<string> topicsToSubscribe;
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "TestTopic1",
|
Topic = "TestTopic1",
|
||||||
Payload = new byte[] { 1, 2, 3, 4 }
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
});
|
});
|
||||||
|
|
||||||
await testEnvironment.Server.InjectApplicationMessage(
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
@@ -109,7 +109,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "TestTopic1",
|
Topic = "TestTopic1",
|
||||||
Payload = new byte[] { 1, 2, 3, 4 }
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
}));
|
}));
|
||||||
|
|
||||||
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
||||||
@@ -137,7 +137,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "TestTopic2",
|
Topic = "TestTopic2",
|
||||||
Payload = new byte[] { 1, 2, 3, 4 }
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
});
|
});
|
||||||
|
|
||||||
await testEnvironment.Server.InjectApplicationMessage(
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
@@ -145,7 +145,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "TestTopic2",
|
Topic = "TestTopic2",
|
||||||
Payload = new byte[] { 1, 2, 3, 4 }
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Ensure first client still works
|
// Ensure first client still works
|
||||||
@@ -153,7 +153,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "TestTopic1",
|
Topic = "TestTopic1",
|
||||||
Payload = new byte[] { 1, 2, 3, 4 }
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
});
|
});
|
||||||
|
|
||||||
await testEnvironment.Server.InjectApplicationMessage(
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
@@ -161,7 +161,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "TestTopic1",
|
Topic = "TestTopic1",
|
||||||
Payload = new byte[] { 1, 2, 3, 4 }
|
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
@@ -178,12 +178,10 @@ namespace MQTTnet.Tests.Server
|
|||||||
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
||||||
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
|
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
|
||||||
.WithTcpServer("localhost", 8883)
|
.WithTcpServer("localhost", 8883)
|
||||||
.WithTls(
|
.WithTlsOptions(
|
||||||
tls =>
|
o =>
|
||||||
{
|
{
|
||||||
tls.UseTls = true;
|
o.WithSslProtocols(SslProtocols.Tls12).WithCertificateValidationHandler(certValidator);
|
||||||
tls.SslProtocol = SslProtocols.Tls12;
|
|
||||||
tls.CertificateValidationHandler = certValidator;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var clientOptions = clientOptionsBuilder.Build();
|
var clientOptions = clientOptionsBuilder.Build();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ namespace MQTTnet.Client
|
|||||||
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
|
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
|
||||||
{
|
{
|
||||||
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
|
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
|
||||||
|
await DisconnectInternal(null, null, connectResult).ConfigureAwait(false);
|
||||||
return connectResult;
|
return connectResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,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);
|
||||||
@@ -303,6 +309,7 @@ namespace MQTTnet.Client
|
|||||||
AuthenticationMethod = Options.AuthenticationMethod,
|
AuthenticationMethod = Options.AuthenticationMethod,
|
||||||
AuthenticationData = data.AuthenticationData,
|
AuthenticationData = data.AuthenticationData,
|
||||||
ReasonString = data.ReasonString,
|
ReasonString = data.ReasonString,
|
||||||
|
ReasonCode = data.ReasonCode,
|
||||||
UserProperties = data.UserProperties
|
UserProperties = data.UserProperties
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -321,14 +328,14 @@ namespace MQTTnet.Client
|
|||||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
|
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThrowIfDisposed();
|
||||||
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfDisposed();
|
|
||||||
ThrowIfNotConnected();
|
|
||||||
|
|
||||||
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
@@ -440,11 +447,19 @@ 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)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Extended authentication handler is not yet supported");
|
||||||
|
}
|
||||||
|
else if (receivedPacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection closed.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Extended authentication handler is not yet supported");
|
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -457,11 +472,10 @@ namespace MQTTnet.Client
|
|||||||
// did send a proper ACK packet with a non success response.
|
// did send a proper ACK packet with a non success response.
|
||||||
if (options.ThrowOnNonSuccessfulConnectResponse)
|
if (options.ThrowOnNonSuccessfulConnectResponse)
|
||||||
{
|
{
|
||||||
_logger.Warning(
|
|
||||||
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
|
|
||||||
|
|
||||||
if (result.ResultCode != MqttClientConnectResultCode.Success)
|
if (result.ResultCode != MqttClientConnectResultCode.Success)
|
||||||
{
|
{
|
||||||
|
_logger.Warning(
|
||||||
|
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
|
||||||
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
|
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,7 +519,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();
|
||||||
@@ -547,7 +561,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
_packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception));
|
||||||
|
|
||||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||||
@@ -695,12 +709,15 @@ namespace MQTTnet.Client
|
|||||||
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
|
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Error while handling application message.");
|
_logger.Error(exception, "Error while handling application message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -774,7 +791,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Verbose("Start receiving packets.");
|
_logger.Verbose("Start receiving packets");
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -812,20 +829,21 @@ 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -847,7 +865,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Error when sending request packet ({0}).", requestPacket.GetType().Name);
|
_logger.Warning(exception, "Error when sending {0} request packet", requestPacket.GetType().Name);
|
||||||
packetAwaitable.Fail(exception);
|
packetAwaitable.Fail(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,7 +877,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (exception is MqttCommunicationTimedOutException)
|
if (exception is MqttCommunicationTimedOutException)
|
||||||
{
|
{
|
||||||
_logger.Warning("Timeout while waiting for response packet ({0}).", typeof(TResponsePacket).Name);
|
_logger.Warning("Timeout while waiting for {0} response packet", typeof(TResponsePacket).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
@@ -925,7 +943,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Error while initiating disconnect.");
|
_logger.Warning(exception, "Error while initiating disconnect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -960,7 +978,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
else if (packet is MqttPingReqPacket)
|
else if (packet is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a Client to the Server only.");
|
throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a client to the server only.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -982,14 +1000,15 @@ 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 processing received packet ({packet.GetType().Name}).");
|
_logger.Error(exception, "Error while processing received {0} packet", packet.GetType().Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetDispatcher.FailAll(exception);
|
// The packet dispatcher may already be gone due to disconnect etc!
|
||||||
|
_packetDispatcher?.FailAll(exception);
|
||||||
|
|
||||||
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -999,7 +1018,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Verbose("Start sending keep alive packets.");
|
_logger.Verbose("Start sending keep alive packets");
|
||||||
|
|
||||||
var keepAlivePeriod = Options.KeepAlivePeriod;
|
var keepAlivePeriod = Options.KeepAlivePeriod;
|
||||||
|
|
||||||
@@ -1037,19 +1056,19 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
else if (exception is MqttCommunicationException)
|
else if (exception is MqttCommunicationException)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets.");
|
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
|
_logger.Error(exception, "Error exception while sending/receiving keep alive packets");
|
||||||
}
|
}
|
||||||
|
|
||||||
await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_logger.Verbose("Stopped sending keep alive packets.");
|
_logger.Verbose("Stopped sending keep alive packets");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class DefaultMqttCertificatesProvider : IMqttClientCertificatesProvider
|
||||||
|
{
|
||||||
|
readonly X509Certificate2Collection _certificates;
|
||||||
|
|
||||||
|
public DefaultMqttCertificatesProvider(X509Certificate2Collection certificates)
|
||||||
|
{
|
||||||
|
_certificates = certificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultMqttCertificatesProvider(IEnumerable<X509Certificate> certificates)
|
||||||
|
{
|
||||||
|
if (certificates != null)
|
||||||
|
{
|
||||||
|
_certificates = new X509Certificate2Collection();
|
||||||
|
foreach (var certificate in certificates)
|
||||||
|
{
|
||||||
|
_certificates.Add(certificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509CertificateCollection GetCertificates()
|
||||||
|
{
|
||||||
|
return _certificates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public interface IMqttClientCertificatesProvider
|
||||||
|
{
|
||||||
|
System.Security.Cryptography.X509Certificates.X509CertificateCollection GetCertificates();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,14 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public X509Chain Chain { get; set; }
|
public X509Chain Chain { get; set; }
|
||||||
|
|
||||||
public SslPolicyErrors SslPolicyErrors { get; set; }
|
|
||||||
|
|
||||||
public IMqttClientChannelOptions ClientOptions { get; set; }
|
public IMqttClientChannelOptions ClientOptions { get; set; }
|
||||||
|
#if NET452 || NET461 || NET48
|
||||||
|
/// <summary>
|
||||||
|
/// Can be a host string name or an object derived from WebRequest.
|
||||||
|
/// </summary>
|
||||||
|
public object Sender { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public SslPolicyErrors SslPolicyErrors { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -15,10 +17,14 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClientOptionsBuilder
|
public sealed class MqttClientOptionsBuilder
|
||||||
{
|
{
|
||||||
readonly MqttClientOptions _options = new MqttClientOptions();
|
readonly MqttClientOptions _options = new MqttClientOptions();
|
||||||
MqttClientWebSocketProxyOptions _proxyOptions;
|
|
||||||
|
[Obsolete] MqttClientWebSocketProxyOptions _proxyOptions;
|
||||||
|
|
||||||
MqttClientTcpOptions _tcpOptions;
|
MqttClientTcpOptions _tcpOptions;
|
||||||
MqttClientOptionsBuilderTlsParameters _tlsParameters;
|
MqttClientTlsOptions _tlsOptions;
|
||||||
|
|
||||||
|
[Obsolete] MqttClientOptionsBuilderTlsParameters _tlsParameters;
|
||||||
|
|
||||||
MqttClientWebSocketOptions _webSocketOptions;
|
MqttClientWebSocketOptions _webSocketOptions;
|
||||||
|
|
||||||
public MqttClientOptions Build()
|
public MqttClientOptions Build()
|
||||||
@@ -28,11 +34,17 @@ namespace MQTTnet.Client
|
|||||||
throw new InvalidOperationException("A channel must be set.");
|
throw new InvalidOperationException("A channel must be set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
var tlsOptions = new MqttClientTlsOptions
|
tlsOptions = new MqttClientTlsOptions
|
||||||
{
|
{
|
||||||
UseTls = true,
|
UseTls = true,
|
||||||
SslProtocol = _tlsParameters.SslProtocol,
|
SslProtocol = _tlsParameters.SslProtocol,
|
||||||
@@ -40,28 +52,23 @@ namespace MQTTnet.Client
|
|||||||
CertificateValidationHandler = _tlsParameters.CertificateValidationHandler,
|
CertificateValidationHandler = _tlsParameters.CertificateValidationHandler,
|
||||||
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
|
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
|
||||||
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors,
|
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors,
|
||||||
#if WINDOWS_UWP
|
ClientCertificatesProvider = _tlsParameters.CertificatesProvider,
|
||||||
Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(),
|
|
||||||
#else
|
|
||||||
Certificates = _tlsParameters.Certificates?.ToList(),
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_tcpOptions != null)
|
|
||||||
{
|
|
||||||
_tcpOptions.TlsOptions = tlsOptions;
|
|
||||||
}
|
|
||||||
else if (_webSocketOptions != null)
|
|
||||||
{
|
|
||||||
_webSocketOptions.TlsOptions = tlsOptions;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_tcpOptions != null)
|
||||||
|
{
|
||||||
|
_tcpOptions.TlsOptions = tlsOptions;
|
||||||
|
}
|
||||||
|
else if (_webSocketOptions != null)
|
||||||
|
{
|
||||||
|
_webSocketOptions.TlsOptions = tlsOptions;
|
||||||
|
}
|
||||||
|
|
||||||
if (_proxyOptions != null)
|
if (_proxyOptions != null)
|
||||||
{
|
{
|
||||||
if (_webSocketOptions == null)
|
if (_webSocketOptions == null)
|
||||||
@@ -78,17 +85,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
return _options;
|
return _options;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
|
|
||||||
/// This will become the default behavior in future versions of the library.
|
|
||||||
/// </summary>
|
|
||||||
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
|
|
||||||
{
|
|
||||||
_options.ThrowOnNonSuccessfulConnectResponse = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
|
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
|
||||||
{
|
{
|
||||||
_options.AuthenticationMethod = method;
|
_options.AuthenticationMethod = method;
|
||||||
@@ -120,6 +117,7 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithTcpServer(... configure) or WithWebSocketServer(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
|
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
|
||||||
{
|
{
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
@@ -136,12 +134,16 @@ namespace MQTTnet.Client
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "mqtts":
|
case "mqtts":
|
||||||
WithTcpServer(uri.Host, port).WithTls();
|
WithTcpServer(uri.Host, port)
|
||||||
|
.WithTlsOptions(
|
||||||
|
o =>
|
||||||
|
{
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "ws":
|
case "ws":
|
||||||
case "wss":
|
case "wss":
|
||||||
WithWebSocketServer(uri.ToString());
|
WithWebSocketServer(o => o.WithUri(uri.ToString()));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -159,6 +161,7 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithTcpServer(... configure) or WithWebSocketServer(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithConnectionUri(string uri)
|
public MqttClientOptionsBuilder WithConnectionUri(string uri)
|
||||||
{
|
{
|
||||||
return WithConnectionUri(new Uri(uri, UriKind.Absolute));
|
return WithConnectionUri(new Uri(uri, UriKind.Absolute));
|
||||||
@@ -222,6 +225,16 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
|
||||||
|
/// This will become the default behavior in future versions of the library.
|
||||||
|
/// </summary>
|
||||||
|
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
|
||||||
|
{
|
||||||
|
_options.ThrowOnNonSuccessfulConnectResponse = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
|
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
|
||||||
{
|
{
|
||||||
if (value == MqttProtocolVersion.Unknown)
|
if (value == MqttProtocolVersion.Unknown)
|
||||||
@@ -233,6 +246,7 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithProxy(
|
public MqttClientOptionsBuilder WithProxy(
|
||||||
string address,
|
string address,
|
||||||
string username = null,
|
string username = null,
|
||||||
@@ -254,6 +268,7 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithProxy(Action<MqttClientWebSocketProxyOptions> optionsBuilder)
|
public MqttClientOptionsBuilder WithProxy(Action<MqttClientWebSocketProxyOptions> optionsBuilder)
|
||||||
{
|
{
|
||||||
if (optionsBuilder == null)
|
if (optionsBuilder == null)
|
||||||
@@ -324,17 +339,20 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithTlsOptions(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
|
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
|
||||||
{
|
{
|
||||||
_tlsParameters = parameters;
|
_tlsParameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithTlsOptions(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithTls()
|
public MqttClientOptionsBuilder WithTls()
|
||||||
{
|
{
|
||||||
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
|
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithTlsOptions(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithTls(Action<MqttClientOptionsBuilderTlsParameters> optionsBuilder)
|
public MqttClientOptionsBuilder WithTls(Action<MqttClientOptionsBuilderTlsParameters> optionsBuilder)
|
||||||
{
|
{
|
||||||
if (optionsBuilder == null)
|
if (optionsBuilder == null)
|
||||||
@@ -351,6 +369,26 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithTlsOptions(MqttClientTlsOptions tlsOptions)
|
||||||
|
{
|
||||||
|
_tlsOptions = tlsOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithTlsOptions(Action<MqttClientTlsOptionsBuilder> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new MqttClientTlsOptionsBuilder();
|
||||||
|
configure.Invoke(builder);
|
||||||
|
|
||||||
|
_tlsOptions = builder.Build();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithTopicAliasMaximum(ushort topicAliasMaximum)
|
public MqttClientOptionsBuilder WithTopicAliasMaximum(ushort topicAliasMaximum)
|
||||||
{
|
{
|
||||||
_options.TopicAliasMaximum = topicAliasMaximum;
|
_options.TopicAliasMaximum = topicAliasMaximum;
|
||||||
@@ -382,6 +420,7 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters = null)
|
public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters = null)
|
||||||
{
|
{
|
||||||
_webSocketOptions = new MqttClientWebSocketOptions
|
_webSocketOptions = new MqttClientWebSocketOptions
|
||||||
@@ -394,6 +433,21 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWebSocketServer(Action<MqttClientWebSocketOptionsBuilder> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
|
var webSocketOptionsBuilder = new MqttClientWebSocketOptionsBuilder();
|
||||||
|
configure.Invoke(webSocketOptionsBuilder);
|
||||||
|
|
||||||
|
_webSocketOptions = webSocketOptionsBuilder.Build();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||||
public MqttClientOptionsBuilder WithWebSocketServer(Action<MqttClientWebSocketOptions> optionsBuilder)
|
public MqttClientOptionsBuilder WithWebSocketServer(Action<MqttClientWebSocketOptions> optionsBuilder)
|
||||||
{
|
{
|
||||||
if (optionsBuilder == null)
|
if (optionsBuilder == null)
|
||||||
@@ -425,6 +479,12 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillMessageExpiryInterval(uint willMessageExpiryInterval)
|
||||||
|
{
|
||||||
|
_options.WillMessageExpiryInterval = willMessageExpiryInterval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithWillPayload(byte[] willPayload)
|
public MqttClientOptionsBuilder WithWillPayload(byte[] willPayload)
|
||||||
{
|
{
|
||||||
_options.WillPayload = willPayload;
|
_options.WillPayload = willPayload;
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use methods from MqttClientOptionsBuilder instead.")]
|
||||||
public sealed class MqttClientOptionsBuilderTlsParameters
|
public sealed class MqttClientOptionsBuilderTlsParameters
|
||||||
{
|
{
|
||||||
|
IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate> _obsoleteCertificates;
|
||||||
|
|
||||||
public bool UseTls { get; set; }
|
public bool UseTls { get; set; }
|
||||||
|
|
||||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
||||||
@@ -24,7 +26,24 @@ namespace MQTTnet.Client
|
|||||||
#if WINDOWS_UWP
|
#if WINDOWS_UWP
|
||||||
public IEnumerable<IEnumerable<byte>> Certificates { get; set; }
|
public IEnumerable<IEnumerable<byte>> Certificates { get; set; }
|
||||||
#else
|
#else
|
||||||
public IEnumerable<X509Certificate> Certificates { get; set; }
|
[Obsolete("Use CertificatesProvider instead.")]
|
||||||
|
public IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate> Certificates
|
||||||
|
{
|
||||||
|
get => _obsoleteCertificates;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_obsoleteCertificates = value;
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
CertificatesProvider = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CertificatesProvider = new DefaultMqttCertificatesProvider(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
@@ -36,5 +55,7 @@ namespace MQTTnet.Client
|
|||||||
public bool IgnoreCertificateChainErrors { get; set; }
|
public bool IgnoreCertificateChainErrors { get; set; }
|
||||||
|
|
||||||
public bool IgnoreCertificateRevocationErrors { get; set; }
|
public bool IgnoreCertificateRevocationErrors { get; set; }
|
||||||
|
|
||||||
|
public IMqttClientCertificatesProvider CertificatesProvider { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use dedicated methods in MqttClientOptionsBuilder.")]
|
||||||
public class MqttClientOptionsBuilderWebSocketParameters
|
public class MqttClientOptionsBuilderWebSocketParameters
|
||||||
{
|
{
|
||||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Client
|
|||||||
return options.Port.Value;
|
return options.Port.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !options.TlsOptions.UseTls ? 1883 : 8883;
|
return !(options.TlsOptions?.UseTls ?? false) ? 1883 : 8883;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online;
|
public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online;
|
||||||
|
|
||||||
#if WINDOWS_UWP
|
/// <summary>
|
||||||
public List<byte[]> Certificates { get; set; }
|
/// Gets or sets the provider for certificates.
|
||||||
#else
|
/// This provider gets called whenever the client wants to connect
|
||||||
public List<X509Certificate> Certificates { get; set; }
|
/// with the server and requires certificates for authentication.
|
||||||
#endif
|
/// The implementation may return different certificates each time.
|
||||||
|
/// </summary>
|
||||||
|
public IMqttClientCertificatesProvider ClientCertificatesProvider { get; set; }
|
||||||
|
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
|
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||||
@@ -48,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
|
using System.Net.Security;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientTlsOptionsBuilder
|
||||||
|
{
|
||||||
|
readonly MqttClientTlsOptions _tlsOptions = new MqttClientTlsOptions
|
||||||
|
{
|
||||||
|
// If someone used this builder the change is very very high that TLS
|
||||||
|
// should be actually used.
|
||||||
|
UseTls = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public MqttClientTlsOptions Build()
|
||||||
|
{
|
||||||
|
return _tlsOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder UseTls(bool useTls = true)
|
||||||
|
{
|
||||||
|
_tlsOptions.UseTls = useTls;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithAllowUntrustedCertificates(bool allowUntrustedCertificates = true)
|
||||||
|
{
|
||||||
|
_tlsOptions.AllowUntrustedCertificates = allowUntrustedCertificates;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithCertificateValidationHandler(Func<MqttClientCertificateValidationEventArgs, bool> certificateValidationHandler)
|
||||||
|
{
|
||||||
|
if (certificateValidationHandler == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(certificateValidationHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tlsOptions.CertificateValidationHandler = certificateValidationHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithClientCertificates(IEnumerable<X509Certificate2> certificates)
|
||||||
|
{
|
||||||
|
if (certificates == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(certificates));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithClientCertificates(X509Certificate2Collection certificates)
|
||||||
|
{
|
||||||
|
if (certificates == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(certificates));
|
||||||
|
}
|
||||||
|
|
||||||
|
_tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithClientCertificatesProvider(IMqttClientCertificatesProvider clientCertificatesProvider)
|
||||||
|
{
|
||||||
|
_tlsOptions.ClientCertificatesProvider = clientCertificatesProvider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithIgnoreCertificateChainErrors(bool ignoreCertificateChainErrors = true)
|
||||||
|
{
|
||||||
|
_tlsOptions.IgnoreCertificateChainErrors = ignoreCertificateChainErrors;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithIgnoreCertificateRevocationErrors(bool ignoreCertificateRevocationErrors = true)
|
||||||
|
{
|
||||||
|
_tlsOptions.IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithRevocationMode(X509RevocationMode revocationMode)
|
||||||
|
{
|
||||||
|
_tlsOptions.RevocationMode = revocationMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithSslProtocols(SslProtocols sslProtocols)
|
||||||
|
{
|
||||||
|
_tlsOptions.SslProtocol = sslProtocols;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithTargetHost(string targetHost)
|
||||||
|
{
|
||||||
|
_tlsOptions.TargetHost = targetHost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
|
public MqttClientTlsOptionsBuilder WithAllowRenegotiation(bool allowRenegotiation = true)
|
||||||
|
{
|
||||||
|
_tlsOptions.AllowRenegotiation = allowRenegotiation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithApplicationProtocols(List<SslApplicationProtocol> applicationProtocols)
|
||||||
|
{
|
||||||
|
_tlsOptions.ApplicationProtocols = applicationProtocols;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(CipherSuitesPolicy cipherSuitePolicy)
|
||||||
|
{
|
||||||
|
_tlsOptions.CipherSuitesPolicy = cipherSuitePolicy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(EncryptionPolicy encryptionPolicy)
|
||||||
|
{
|
||||||
|
_tlsOptions.EncryptionPolicy = encryptionPolicy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if NET7_0_OR_GREATER
|
||||||
|
public MqttClientTlsOptionsBuilder WithTrustChain(X509Certificate2Collection chain)
|
||||||
|
{
|
||||||
|
_tlsOptions.TrustChain = chain;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientWebSocketOptionsBuilder
|
||||||
|
{
|
||||||
|
readonly MqttClientWebSocketOptions _webSocketOptions = new MqttClientWebSocketOptions();
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptions Build()
|
||||||
|
{
|
||||||
|
return _webSocketOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithCookieContainer(CookieContainer cookieContainer)
|
||||||
|
{
|
||||||
|
_webSocketOptions.CookieContainer = cookieContainer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithCookieContainer(ICredentials credentials)
|
||||||
|
{
|
||||||
|
_webSocketOptions.Credentials = credentials;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithProxyOptions(MqttClientWebSocketProxyOptions proxyOptions)
|
||||||
|
{
|
||||||
|
_webSocketOptions.ProxyOptions = proxyOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithProxyOptions(Action<MqttClientWebSocketProxyOptionsBuilder> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxyOptionsBuilder = new MqttClientWebSocketProxyOptionsBuilder();
|
||||||
|
configure.Invoke(proxyOptionsBuilder);
|
||||||
|
|
||||||
|
_webSocketOptions.ProxyOptions = proxyOptionsBuilder.Build();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithRequestHeaders(IDictionary<string, string> requestHeaders)
|
||||||
|
{
|
||||||
|
_webSocketOptions.RequestHeaders = requestHeaders;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithSubProtocols(ICollection<string> subProtocols)
|
||||||
|
{
|
||||||
|
_webSocketOptions.SubProtocols = subProtocols;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithUri(string uri)
|
||||||
|
{
|
||||||
|
_webSocketOptions.Uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithKeepAliveInterval(TimeSpan keepAliveInterval)
|
||||||
|
{
|
||||||
|
_webSocketOptions.KeepAliveInterval = keepAliveInterval;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if !WINDOWS_UWP && !NETSTANDARD1_3
|
||||||
|
public MqttClientWebSocketOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true)
|
||||||
|
{
|
||||||
|
_webSocketOptions.UseDefaultCredentials = useDefaultCredentials;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientWebSocketProxyOptionsBuilder
|
||||||
|
{
|
||||||
|
readonly MqttClientWebSocketProxyOptions _proxyOptions = new MqttClientWebSocketProxyOptions();
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithAddress(string address)
|
||||||
|
{
|
||||||
|
_proxyOptions.Address = address;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithUsername(string username)
|
||||||
|
{
|
||||||
|
_proxyOptions.Username = username;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithPassword(string password)
|
||||||
|
{
|
||||||
|
_proxyOptions.Password = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithDomain(string domain)
|
||||||
|
{
|
||||||
|
_proxyOptions.Domain = domain;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithBypassOnLocal(bool bypassOnLocal = true)
|
||||||
|
{
|
||||||
|
_proxyOptions.BypassOnLocal = bypassOnLocal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithBypassList(string[] bypassList)
|
||||||
|
{
|
||||||
|
_proxyOptions.BypassList = bypassList;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithBypassList(IEnumerable<string> bypassList)
|
||||||
|
{
|
||||||
|
_proxyOptions.BypassList = bypassList?.ToArray();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true)
|
||||||
|
{
|
||||||
|
_proxyOptions.UseDefaultCredentials = useDefaultCredentials;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptions Build()
|
||||||
|
{
|
||||||
|
return _proxyOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,45 +5,107 @@
|
|||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public sealed class MqttDisconnectPacketFactory
|
public sealed class MqttDisconnectPacketFactory
|
||||||
{
|
{
|
||||||
readonly MqttDisconnectPacket _normalDisconnection = new MqttDisconnectPacket
|
static readonly MqttDisconnectPacket DefaultNormalDisconnection = new MqttDisconnectPacket
|
||||||
{
|
{
|
||||||
ReasonCode = MqttDisconnectReasonCode.NormalDisconnection
|
ReasonCode = MqttDisconnectReasonCode.NormalDisconnection,
|
||||||
|
UserProperties = null,
|
||||||
|
ReasonString = null,
|
||||||
|
ServerReference = null,
|
||||||
|
SessionExpiryInterval = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly MqttDisconnectPacket DefaultServerShuttingDown = new MqttDisconnectPacket
|
||||||
|
{
|
||||||
|
ReasonCode = MqttDisconnectReasonCode.ServerShuttingDown,
|
||||||
|
UserProperties = null,
|
||||||
|
ReasonString = null,
|
||||||
|
ServerReference = null,
|
||||||
|
SessionExpiryInterval = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly MqttDisconnectPacket DefaultUnspecifiedError = new MqttDisconnectPacket
|
||||||
|
{
|
||||||
|
ReasonCode = MqttDisconnectReasonCode.UnspecifiedError,
|
||||||
|
UserProperties = null,
|
||||||
|
ReasonString = null,
|
||||||
|
ServerReference = null,
|
||||||
|
SessionExpiryInterval = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
public MqttDisconnectPacket Create(MqttDisconnectReasonCode reasonCode)
|
public MqttDisconnectPacket Create(MqttDisconnectReasonCode reasonCode)
|
||||||
{
|
{
|
||||||
if (reasonCode == MqttDisconnectReasonCode.NormalDisconnection)
|
if (reasonCode == MqttDisconnectReasonCode.NormalDisconnection)
|
||||||
{
|
{
|
||||||
return _normalDisconnection;
|
return DefaultNormalDisconnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reasonCode == MqttDisconnectReasonCode.ServerShuttingDown)
|
||||||
|
{
|
||||||
|
return DefaultServerShuttingDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reasonCode == MqttDisconnectReasonCode.UnspecifiedError)
|
||||||
|
{
|
||||||
|
return DefaultUnspecifiedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MqttDisconnectPacket
|
return new MqttDisconnectPacket
|
||||||
{
|
{
|
||||||
ReasonCode = reasonCode
|
ReasonCode = reasonCode,
|
||||||
|
UserProperties = null,
|
||||||
|
ReasonString = null,
|
||||||
|
ServerReference = null,
|
||||||
|
SessionExpiryInterval = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttDisconnectPacket Create(MqttServerStopOptions serverStopOptions)
|
||||||
|
{
|
||||||
|
if (serverStopOptions == null)
|
||||||
|
{
|
||||||
|
return DefaultServerShuttingDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Create(serverStopOptions.DefaultClientDisconnectOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttDisconnectPacket Create(MqttServerClientDisconnectOptions clientDisconnectOptions)
|
||||||
|
{
|
||||||
|
if (clientDisconnectOptions == null)
|
||||||
|
{
|
||||||
|
return DefaultNormalDisconnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttDisconnectPacket
|
||||||
|
{
|
||||||
|
ReasonCode = clientDisconnectOptions.ReasonCode,
|
||||||
|
UserProperties = clientDisconnectOptions.UserProperties,
|
||||||
|
ReasonString = clientDisconnectOptions.ReasonString,
|
||||||
|
ServerReference = clientDisconnectOptions.ServerReference,
|
||||||
|
SessionExpiryInterval = 0 // TODO: Not yet supported!
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttDisconnectPacket Create(MqttClientDisconnectOptions clientDisconnectOptions)
|
public MqttDisconnectPacket Create(MqttClientDisconnectOptions clientDisconnectOptions)
|
||||||
{
|
{
|
||||||
var packet = new MqttDisconnectPacket();
|
|
||||||
|
|
||||||
if (clientDisconnectOptions == null)
|
if (clientDisconnectOptions == null)
|
||||||
{
|
{
|
||||||
packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection;
|
return DefaultNormalDisconnection;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
packet.ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason;
|
|
||||||
packet.UserProperties = clientDisconnectOptions.UserProperties;
|
|
||||||
packet.SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return packet;
|
return new MqttDisconnectPacket
|
||||||
|
{
|
||||||
|
ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason,
|
||||||
|
UserProperties = clientDisconnectOptions.UserProperties,
|
||||||
|
SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -18,6 +16,7 @@ using Windows.Security.Cryptography.Certificates;
|
|||||||
using MQTTnet.Channel;
|
using MQTTnet.Channel;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
@@ -126,17 +125,19 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
private static Certificate LoadCertificate(IMqttClientChannelOptions options)
|
private static Certificate LoadCertificate(IMqttClientChannelOptions options)
|
||||||
{
|
{
|
||||||
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
|
var certificates = options.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
|
||||||
|
|
||||||
|
if (certificates == null || certificates.Count == 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.TlsOptions.Certificates.Count > 1)
|
if (certificates.Count > 1)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Only one client certificate is supported when using 'uap10.0'.");
|
throw new NotSupportedException("Only one client certificate is supported when using 'uap10.0'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Certificate(options.TlsOptions.Certificates.First().AsBuffer());
|
return new Certificate(certificates[0].Export(X509ContentType.Cert).AsBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<ChainValidationResult> ResolveIgnorableServerCertificateErrors()
|
private IEnumerable<ChainValidationResult> ResolveIgnorableServerCertificateErrors()
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
#if !WINDOWS_UWP
|
#if !WINDOWS_UWP
|
||||||
using MQTTnet.Channel;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
@@ -14,6 +11,9 @@ using System.Runtime.ExceptionServices;
|
|||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Channel;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
@@ -73,7 +73,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
socket.Bind(_tcpOptions.LocalEndpoint);
|
socket.Bind(_tcpOptions.LocalEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
|
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
|
||||||
socket.SendBufferSize = _tcpOptions.BufferSize;
|
socket.SendBufferSize = _tcpOptions.BufferSize;
|
||||||
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
|
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
|
||||||
@@ -105,7 +105,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
targetHost = _tcpOptions.Server;
|
targetHost = _tcpOptions.Server;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -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(
|
||||||
@@ -298,18 +315,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
X509CertificateCollection LoadCertificates()
|
X509CertificateCollection LoadCertificates()
|
||||||
{
|
{
|
||||||
if (_tcpOptions.TlsOptions.Certificates == null)
|
return _tcpOptions.TlsOptions.ClientCertificatesProvider?.GetCertificates();
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var certificates = new X509CertificateCollection();
|
|
||||||
foreach (var certificate in _tcpOptions.TlsOptions.Certificates)
|
|
||||||
{
|
|
||||||
certificates.Add(certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return certificates;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
|
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
|
||||||
{
|
{
|
||||||
|
clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation;
|
||||||
await clientHandler(clientAdapter).ConfigureAwait(false);
|
await clientHandler(clientAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,6 +256,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// ReSharper disable once MethodHasAsyncOverload
|
||||||
stream?.Dispose();
|
stream?.Dispose();
|
||||||
clientSocket?.Dispose();
|
clientSocket?.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,26 +206,34 @@ namespace MQTTnet.Implementations
|
|||||||
clientWebSocket.Options.Cookies = _options.CookieContainer;
|
clientWebSocket.Options.Cookies = _options.CookieContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
|
if (_options.TlsOptions?.UseTls == true)
|
||||||
{
|
{
|
||||||
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
|
var certificates = _options.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
|
||||||
foreach (var certificate in _options.TlsOptions.Certificates)
|
if (certificates?.Count > 0)
|
||||||
{
|
{
|
||||||
#if WINDOWS_UWP
|
clientWebSocket.Options.ClientCertificates = certificates;
|
||||||
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
|
|
||||||
#else
|
|
||||||
clientWebSocket.Options.ClientCertificates.Add(certificate);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !NETSTANDARD1_3
|
#if !NETSTANDARD1_3
|
||||||
#if !WINDOWS_UWP
|
#if !WINDOWS_UWP
|
||||||
clientWebSocket.Options.UseDefaultCredentials = _options.UseDefaultCredentials;
|
// Only set the value if it is actually true. This property is not supported on all platforms
|
||||||
|
// and will throw a _PlatformNotSupported_ (i.e. WASM) exception when being used regardless of the actual value.
|
||||||
|
if (_options.UseDefaultCredentials)
|
||||||
|
{
|
||||||
|
clientWebSocket.Options.UseDefaultCredentials = _options.UseDefaultCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_options.KeepAliveInterval != WebSocket.DefaultKeepAliveInterval)
|
||||||
|
{
|
||||||
|
clientWebSocket.Options.KeepAliveInterval = _options.KeepAliveInterval;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
clientWebSocket.Options.KeepAliveInterval = _options.KeepAliveInterval;
|
|
||||||
#endif
|
#endif
|
||||||
clientWebSocket.Options.Credentials = _options.Credentials;
|
if (_options.Credentials != null)
|
||||||
|
{
|
||||||
|
clientWebSocket.Options.Credentials = _options.Credentials;
|
||||||
|
}
|
||||||
|
|
||||||
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
|
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
|
||||||
if (certificateValidationHandler != null)
|
if (certificateValidationHandler != null)
|
||||||
@@ -236,12 +244,20 @@ namespace MQTTnet.Implementations
|
|||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
|
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
|
||||||
#elif WINDOWS_UWP
|
#elif WINDOWS_UWP
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
|
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
|
||||||
#elif NET452
|
#elif NET452 || NET461 || NET48
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
|
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
#elif NET461
|
{
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
|
var context = new MqttClientCertificateValidationEventArgs
|
||||||
#elif NET48
|
{
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net48'.");
|
Sender = sender,
|
||||||
|
Certificate = certificate,
|
||||||
|
Chain = chain,
|
||||||
|
SslPolicyErrors = sslPolicyErrors,
|
||||||
|
ClientOptions = _options
|
||||||
|
};
|
||||||
|
|
||||||
|
return certificateValidationHandler(context);
|
||||||
|
};
|
||||||
#else
|
#else
|
||||||
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
{
|
{
|
||||||
@@ -260,4 +276,4 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
|
|
||||||
namespace MQTTnet.Internal
|
|
||||||
{
|
|
||||||
public static class MqttTaskTimeout
|
|
||||||
{
|
|
||||||
public static async Task WaitAsync(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (action == null) throw new ArgumentNullException(nameof(action));
|
|
||||||
|
|
||||||
using (var timeoutCts = new CancellationTokenSource(timeout))
|
|
||||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await action(linkedCts.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException exception)
|
|
||||||
{
|
|
||||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
|
||||||
if (timeoutReached)
|
|
||||||
{
|
|
||||||
throw new MqttCommunicationTimedOutException(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -17,4 +17,5 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cmanagement/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cmanagement/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Coptions/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Coptions/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstatus/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstatus/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstopping/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstorage/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstorage/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -11,6 +11,7 @@ using MQTTnet.Diagnostics;
|
|||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.LowLevelClient;
|
using MQTTnet.LowLevelClient;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
using MqttClient = MQTTnet.Client.MqttClient;
|
using MqttClient = MQTTnet.Client.MqttClient;
|
||||||
|
|
||||||
namespace MQTTnet
|
namespace MQTTnet
|
||||||
@@ -175,6 +176,16 @@ namespace MQTTnet
|
|||||||
return new MqttServer(options, serverAdapters, DefaultLogger);
|
return new MqttServer(options, serverAdapters, DefaultLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptionsBuilder CreateMqttServerClientDisconnectOptionsBuilder()
|
||||||
|
{
|
||||||
|
return new MqttServerClientDisconnectOptionsBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerStopOptionsBuilder CreateMqttServerStopOptionsBuilder()
|
||||||
|
{
|
||||||
|
return new MqttServerStopOptionsBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
public MqttServerOptionsBuilder CreateServerOptionsBuilder()
|
public MqttServerOptionsBuilder CreateServerOptionsBuilder()
|
||||||
{
|
{
|
||||||
return new MqttServerOptionsBuilder();
|
return new MqttServerOptionsBuilder();
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server.Disconnecting
|
||||||
|
{
|
||||||
|
public sealed class MqttServerClientDisconnectOptions
|
||||||
|
{
|
||||||
|
public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The reason string is sent to every client via a DISCONNECT packet.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ReasonString { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The server reference is sent to every client via a DISCONNECT packet.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ServerReference { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// These user properties are sent to every client via a DISCONNECT packet.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server.Disconnecting
|
||||||
|
{
|
||||||
|
public sealed class MqttServerClientDisconnectOptionsBuilder
|
||||||
|
{
|
||||||
|
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptions Build()
|
||||||
|
{
|
||||||
|
return _options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
|
||||||
|
{
|
||||||
|
_options.ReasonCode = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||||
|
{
|
||||||
|
_options.ReasonString = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
|
||||||
|
{
|
||||||
|
_options.ServerReference = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
|
||||||
|
{
|
||||||
|
_options.UserProperties = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||||
|
{
|
||||||
|
if (_options.UserProperties == null)
|
||||||
|
{
|
||||||
|
_options.UserProperties = new List<MqttUserProperty>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_options.UserProperties.Add(new MqttUserProperty(name, value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -14,6 +15,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -95,8 +97,9 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var cancellationToken = _cancellationToken.Token;
|
var cancellationToken = _cancellationToken.Token;
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
|
||||||
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
|
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -104,6 +107,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
|
Session.DisconnectedTimestamp = DateTime.UtcNow;
|
||||||
|
|
||||||
_cancellationToken?.TryCancel();
|
_cancellationToken?.TryCancel();
|
||||||
_cancellationToken?.Dispose();
|
_cancellationToken?.Dispose();
|
||||||
_cancellationToken = null;
|
_cancellationToken = null;
|
||||||
@@ -139,7 +144,7 @@ namespace MQTTnet.Server
|
|||||||
Statistics.HandleSentPacket(packet);
|
Statistics.HandleSentPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync(MqttDisconnectReasonCode reason)
|
public async Task StopAsync(MqttServerClientDisconnectOptions disconnectOptions)
|
||||||
{
|
{
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
@@ -148,16 +153,20 @@ namespace MQTTnet.Server
|
|||||||
// Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+.
|
// Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+.
|
||||||
if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
||||||
{
|
{
|
||||||
// The Client or Server MAY send a DISCONNECT packet before closing the Network Connection.
|
// From RFC: The Client or Server MAY send a DISCONNECT packet before closing the Network Connection.
|
||||||
// This library does not sent a DISCONNECT packet for a normal disconnection. Maybe adding
|
// This library does not sent a DISCONNECT packet for a normal disconnection.
|
||||||
// a configuration option is requested in the future.
|
// TODO: Maybe adding a configuration option is requested in the future.
|
||||||
if (reason != MqttDisconnectReasonCode.NormalDisconnection)
|
if (disconnectOptions != null)
|
||||||
{
|
{
|
||||||
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
|
if (disconnectOptions.ReasonCode != MqttDisconnectReasonCode.NormalDisconnection || disconnectOptions.UserProperties?.Any() == true ||
|
||||||
// token because the entire connection is closed (disposed) as soon as the cancellation
|
!string.IsNullOrEmpty(disconnectOptions.ReasonString) || !string.IsNullOrEmpty(disconnectOptions.ServerReference))
|
||||||
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
{
|
||||||
// at the client!
|
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
|
||||||
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
|
// token because the entire connection is closed (disposed) as soon as the cancellation
|
||||||
|
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
||||||
|
// at the client!
|
||||||
|
await TrySendDisconnectPacket(disconnectOptions).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +226,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (dispatchApplicationMessageResult.CloseConnection)
|
if (dispatchApplicationMessageResult.CloseConnection)
|
||||||
{
|
{
|
||||||
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
await StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.UnspecifiedError });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,14 +557,14 @@ namespace MQTTnet.Server
|
|||||||
_cancellationToken?.TryCancel();
|
_cancellationToken?.TryCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
async Task TrySendDisconnectPacket(MqttServerClientDisconnectOptions options)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// This also indicates that it was tried at least!
|
// This also indicates that it was tried at least!
|
||||||
_disconnectPacketSent = true;
|
_disconnectPacketSent = true;
|
||||||
|
|
||||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(reasonCode);
|
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
||||||
{
|
{
|
||||||
@@ -564,7 +573,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1})", Id, reasonCode);
|
_logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1})", Id, options.ReasonCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -32,11 +33,11 @@ namespace MQTTnet.Server
|
|||||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
|
|
||||||
|
readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim();
|
||||||
|
|
||||||
// The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only.
|
// The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only.
|
||||||
// See the MqttSubscription object for a detailed explanation.
|
// See the MqttSubscription object for a detailed explanation.
|
||||||
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
|
readonly MqttSessionsStorage _sessionsStorage = new MqttSessionsStorage();
|
||||||
|
|
||||||
readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim();
|
|
||||||
readonly HashSet<MqttSession> _subscriberSessions = new HashSet<MqttSession>();
|
readonly HashSet<MqttSession> _subscriberSessions = new HashSet<MqttSession>();
|
||||||
|
|
||||||
public MqttClientSessionsManager(
|
public MqttClientSessionsManager(
|
||||||
@@ -58,8 +59,13 @@ namespace MQTTnet.Server
|
|||||||
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
|
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CloseAllConnections()
|
public async Task CloseAllConnections(MqttServerClientDisconnectOptions options)
|
||||||
{
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
List<MqttClient> connections;
|
List<MqttClient> connections;
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
{
|
{
|
||||||
@@ -69,7 +75,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
foreach (var connection in connections)
|
foreach (var connection in connections)
|
||||||
{
|
{
|
||||||
await connection.StopAsync(MqttDisconnectReasonCode.NormalDisconnection).ConfigureAwait(false);
|
await connection.StopAsync(options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,21 +84,16 @@ namespace MQTTnet.Server
|
|||||||
_logger.Verbose("Deleting session for client '{0}'.", clientId);
|
_logger.Verbose("Deleting session for client '{0}'.", clientId);
|
||||||
|
|
||||||
MqttClient connection;
|
MqttClient connection;
|
||||||
|
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
{
|
{
|
||||||
_clients.TryGetValue(clientId, out connection);
|
_clients.TryGetValue(clientId, out connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttSession session;
|
MqttSession session;
|
||||||
|
|
||||||
_sessionsManagementLock.EnterWriteLock();
|
_sessionsManagementLock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_sessions.TryGetValue(clientId, out session);
|
if (_sessionsStorage.TryRemoveSession(clientId, out session))
|
||||||
_sessions.Remove(clientId);
|
|
||||||
|
|
||||||
if (session != null)
|
|
||||||
{
|
{
|
||||||
_subscriberSessions.Remove(session);
|
_subscriberSessions.Remove(session);
|
||||||
}
|
}
|
||||||
@@ -106,12 +107,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
await connection.StopAsync(MqttDisconnectReasonCode.NormalDisconnection).ConfigureAwait(false);
|
await connection.StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.NormalDisconnection }).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, $"Error while deleting session '{clientId}'.");
|
_logger.Error(exception, "Error while deleting session '{0}'", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -124,12 +125,12 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, $"Error while executing session deleted event for session '{clientId}'.");
|
_logger.Error(exception, "Error while executing session deleted event for session '{0}'", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
session?.Dispose();
|
session?.Dispose();
|
||||||
|
|
||||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
_logger.Verbose("Session of client '{0}' deleted", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
||||||
@@ -209,7 +210,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_eventContainer.InterceptingClientEnqueueEvent.HasHandlers)
|
if (_eventContainer.InterceptingClientEnqueueEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new InterceptingClientApplicationMessageEnqueueEventArgs(senderId, session.Id, applicationMessage);
|
var eventArgs = new InterceptingClientApplicationMessageEnqueueEventArgs(senderId, session.Id, applicationMessage);
|
||||||
@@ -269,20 +270,14 @@ namespace MQTTnet.Server
|
|||||||
_sessionsManagementLock.EnterWriteLock();
|
_sessionsManagementLock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var sessionItem in _sessions)
|
_sessionsStorage.Dispose();
|
||||||
{
|
|
||||||
sessionItem.Value.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_sessionsManagementLock.ExitWriteLock();
|
_sessionsManagementLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_sessionsManagementLock != null)
|
_sessionsManagementLock?.Dispose();
|
||||||
{
|
|
||||||
_sessionsManagementLock.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClient GetClient(string id)
|
public MqttClient GetClient(string id)
|
||||||
@@ -333,9 +328,9 @@ namespace MQTTnet.Server
|
|||||||
_sessionsManagementLock.EnterReadLock();
|
_sessionsManagementLock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var sessionItem in _sessions)
|
foreach (var session in _sessionsStorage.ReadAllSessions())
|
||||||
{
|
{
|
||||||
var sessionStatus = new MqttSessionStatus(sessionItem.Value);
|
var sessionStatus = new MqttSessionStatus(session);
|
||||||
result.Add(sessionStatus);
|
result.Add(sessionStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,7 +372,8 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientConnectedEventArgs(connectPacket,
|
var eventArgs = new ClientConnectedEventArgs(
|
||||||
|
connectPacket,
|
||||||
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
||||||
channelAdapter.Endpoint,
|
channelAdapter.Endpoint,
|
||||||
client.Session.Items);
|
client.Session.Items);
|
||||||
@@ -484,7 +480,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (!_options.EnablePersistentSessions)
|
if (!_options.EnablePersistentSessions)
|
||||||
{
|
{
|
||||||
_sessions.Clear();
|
_sessionsStorage.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,32 +580,34 @@ namespace MQTTnet.Server
|
|||||||
MqttSession session;
|
MqttSession session;
|
||||||
|
|
||||||
// Create a new session (if required).
|
// Create a new session (if required).
|
||||||
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
|
if (!_sessionsStorage.TryGetSession(connectPacket.ClientId, out oldSession))
|
||||||
{
|
{
|
||||||
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
|
session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (connectPacket.CleanSession)
|
if (connectPacket.CleanSession)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId);
|
_logger.Verbose("Deleting existing session of client '{0}' due to clean start", connectPacket.ClientId);
|
||||||
_subscriberSessions.Remove(oldSession);
|
_subscriberSessions.Remove(oldSession);
|
||||||
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
|
session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose("Reusing existing session of client '{0}'", connectPacket.ClientId);
|
||||||
session = oldSession;
|
session = oldSession;
|
||||||
oldSession = null;
|
oldSession = null;
|
||||||
|
|
||||||
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
||||||
session.IsPersistent = sessionShouldPersist;
|
session.IsPersistent = sessionShouldPersist;
|
||||||
connAckPacket.IsSessionPresent = true;
|
session.DisconnectedTimestamp = null;
|
||||||
session.Recover();
|
session.Recover();
|
||||||
|
|
||||||
|
connAckPacket.IsSessionPresent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessions[connectPacket.ClientId] = session;
|
_sessionsStorage.UpdateSession(connectPacket.ClientId, session);
|
||||||
|
|
||||||
// Create a new client (always required).
|
// Create a new client (always required).
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
@@ -640,7 +638,8 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (oldClient != null)
|
if (oldClient != null)
|
||||||
{
|
{
|
||||||
await oldClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
|
// TODO: Consider event here for session takeover to allow manipulation of user properties etc.
|
||||||
|
await oldClient.StopAsync(new MqttServerClientDisconnectOptions { ReasonCode = MqttDisconnectReasonCode.SessionTakenOver }).ConfigureAwait(false);
|
||||||
|
|
||||||
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
|
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
@@ -656,11 +655,14 @@ namespace MQTTnet.Server
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent)
|
MqttSession CreateSession(MqttConnectPacket connectPacket, ValidatingConnectionEventArgs validatingConnectionEventArgs, bool isPersistent)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Created new session for client '{0}'.", clientId);
|
_logger.Verbose("Created new session for client '{0}'", connectPacket.ClientId);
|
||||||
|
|
||||||
return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this);
|
return new MqttSession(connectPacket, validatingConnectionEventArgs.SessionItems, _options, _eventContainer, _retainedMessagesManager, this)
|
||||||
|
{
|
||||||
|
IsPersistent = isPersistent
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId)
|
async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId)
|
||||||
@@ -679,7 +681,7 @@ namespace MQTTnet.Server
|
|||||||
_sessionsManagementLock.EnterReadLock();
|
_sessionsManagementLock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_sessions.TryGetValue(clientId, out var session))
|
if (!_sessionsStorage.TryGetSession(clientId, out var session))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||||
}
|
}
|
||||||
@@ -740,4 +742,4 @@ namespace MQTTnet.Server
|
|||||||
return eventArgs;
|
return eventArgs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Statistics
|
struct Statistics
|
||||||
{
|
{
|
||||||
public long _receivedPacketsCount;
|
public long _receivedPacketsCount;
|
||||||
public long _sentPacketsCount;
|
public long _sentPacketsCount;
|
||||||
|
|||||||
@@ -162,10 +162,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_subscriptionsLock != null)
|
_subscriptionsLock?.Dispose();
|
||||||
{
|
|
||||||
_subscriptionsLock.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
return Task.FromResult(message);
|
return Task.FromResult(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<MqttApplicationMessage>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClearMessages()
|
public async Task ClearMessages()
|
||||||
|
|||||||
@@ -8,11 +8,20 @@ using System.Threading.Tasks;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttServerKeepAliveMonitor
|
public sealed class MqttServerKeepAliveMonitor
|
||||||
{
|
{
|
||||||
|
static readonly MqttServerClientDisconnectOptions DefaultDisconnectOptions = new MqttServerClientDisconnectOptions
|
||||||
|
{
|
||||||
|
ReasonCode = MqttDisconnectReasonCode.KeepAliveTimeout,
|
||||||
|
UserProperties = null,
|
||||||
|
ReasonString = null,
|
||||||
|
ServerReference = null
|
||||||
|
};
|
||||||
|
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttServerOptions _options;
|
readonly MqttServerOptions _options;
|
||||||
readonly MqttClientSessionsManager _sessionsManager;
|
readonly MqttClientSessionsManager _sessionsManager;
|
||||||
@@ -43,7 +52,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Info("Starting keep alive monitor.");
|
_logger.Info("Starting keep alive monitor");
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -56,11 +65,11 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Unhandled exception while checking keep alive timeouts.");
|
_logger.Error(exception, "Unhandled exception while checking keep alive timeouts");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_logger.Verbose("Stopped checking keep alive timeout.");
|
_logger.Verbose("Stopped checking keep alive timeout");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,18 +126,18 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", connection.Id);
|
_logger.Warning("Client '{0}': Did not receive any packet or keep alive signal", connection.Id);
|
||||||
|
|
||||||
// Execute the disconnection in background so that the keep alive monitor can continue
|
// Execute the disconnection in background so that the keep alive monitor can continue
|
||||||
// with checking other connections.
|
// with checking other connections.
|
||||||
// We do not need to wait for the task so no await is needed.
|
// We do not need to wait for the task so no await is needed.
|
||||||
// Also the internal state of the connection must be swapped to "Finalizing" because the
|
// Also the internal state of the connection must be swapped to "Finalizing" because the
|
||||||
// next iteration of the keep alive timer happens.
|
// next iteration of the keep alive timer happens.
|
||||||
var _ = connection.StopAsync(MqttDisconnectReasonCode.KeepAliveTimeout);
|
_ = connection.StopAsync(DefaultDisconnectOptions);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id);
|
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts", connection.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace MQTTnet.Server
|
|||||||
readonly MqttPacketBus _packetBus = new MqttPacketBus();
|
readonly MqttPacketBus _packetBus = new MqttPacketBus();
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
|
|
||||||
|
readonly MqttConnectPacket _connectPacket;
|
||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
||||||
|
|
||||||
@@ -31,18 +32,16 @@ namespace MQTTnet.Server
|
|||||||
HashSet<string> _subscribedTopics;
|
HashSet<string> _subscribedTopics;
|
||||||
|
|
||||||
public MqttSession(
|
public MqttSession(
|
||||||
string clientId,
|
MqttConnectPacket connectPacket,
|
||||||
bool isPersistent,
|
|
||||||
IDictionary items,
|
IDictionary items,
|
||||||
MqttServerOptions serverOptions,
|
MqttServerOptions serverOptions,
|
||||||
MqttServerEventContainer eventContainer,
|
MqttServerEventContainer eventContainer,
|
||||||
MqttRetainedMessagesManager retainedMessagesManager,
|
MqttRetainedMessagesManager retainedMessagesManager,
|
||||||
MqttClientSessionsManager clientSessionsManager)
|
MqttClientSessionsManager clientSessionsManager)
|
||||||
{
|
{
|
||||||
Id = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
|
||||||
IsPersistent = isPersistent;
|
|
||||||
Items = items ?? throw new ArgumentNullException(nameof(items));
|
Items = items ?? throw new ArgumentNullException(nameof(items));
|
||||||
|
|
||||||
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||||
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
|
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
|
||||||
|
|
||||||
@@ -50,10 +49,14 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
|
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public DateTime? DisconnectedTimestamp { get; set; }
|
||||||
|
|
||||||
public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0;
|
public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0;
|
||||||
|
|
||||||
public string Id { get; }
|
public uint ExpiryInterval => _connectPacket.SessionExpiryInterval;
|
||||||
|
|
||||||
|
public string Id => _connectPacket.ClientId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
|
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttSessionsStorage
|
||||||
|
{
|
||||||
|
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
// Make sure that the sessions are also getting disposed!
|
||||||
|
// This object can be reused after disposing.
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var sessionItem in _sessions)
|
||||||
|
{
|
||||||
|
sessionItem.Value.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_sessions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<MqttSession> ReadAllSessions()
|
||||||
|
{
|
||||||
|
return new ReadOnlyCollection<MqttSession>(_sessions.Values.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSession(string id, out MqttSession session)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_sessions.TryGetValue(id, out session))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Session Expiry Interval is 0xFFFFFFFF (UINT_MAX), the Session does not expire.
|
||||||
|
if (session.ExpiryInterval > 0 && session.ExpiryInterval != uint.MaxValue)
|
||||||
|
{
|
||||||
|
if (session.DisconnectedTimestamp.HasValue)
|
||||||
|
{
|
||||||
|
if (DateTime.UtcNow > session.DisconnectedTimestamp.Value.AddSeconds(session.ExpiryInterval))
|
||||||
|
{
|
||||||
|
TryRemoveSession(id, out var oldSession);
|
||||||
|
oldSession?.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryRemoveSession(string id, out MqttSession session)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sessions.TryGetValue(id, out session))
|
||||||
|
{
|
||||||
|
_sessions.Remove(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSession(string id, MqttSession session)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
_sessions[id] = session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ using MQTTnet.Diagnostics;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -29,6 +30,7 @@ namespace MQTTnet.Server
|
|||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
|
|
||||||
CancellationTokenSource _cancellationTokenSource;
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
|
bool _isStopping;
|
||||||
|
|
||||||
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
@@ -169,6 +171,13 @@ namespace MQTTnet.Server
|
|||||||
remove => _eventContainer.ValidatingConnectionEvent.RemoveHandler(value);
|
remove => _eventContainer.ValidatingConnectionEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the server will accept new connections.
|
||||||
|
/// If not, the server will close the connection without any notification (DISCONNECT packet).
|
||||||
|
/// This feature can be used when the server is shutting down.
|
||||||
|
/// </summary>
|
||||||
|
public bool AcceptNewConnections { get; set; } = true;
|
||||||
|
|
||||||
public bool IsStarted => _cancellationTokenSource != null;
|
public bool IsStarted => _cancellationTokenSource != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -184,16 +193,21 @@ namespace MQTTnet.Server
|
|||||||
return _retainedMessagesManager?.ClearMessages() ?? CompletedTask.Instance;
|
return _retainedMessagesManager?.ClearMessages() ?? CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectClientAsync(string id, MqttDisconnectReasonCode reasonCode)
|
public Task DisconnectClientAsync(string id, MqttServerClientDisconnectOptions options)
|
||||||
{
|
{
|
||||||
if (id == null)
|
if (id == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(id));
|
throw new ArgumentNullException(nameof(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
|
return _clientSessionsManager.GetClient(id).StopAsync(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<MqttClientStatus>> GetClientsAsync()
|
public Task<IList<MqttClientStatus>> GetClientsAsync()
|
||||||
@@ -203,13 +217,6 @@ namespace MQTTnet.Server
|
|||||||
return _clientSessionsManager.GetClientsStatus();
|
return _clientSessionsManager.GetClientsStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
|
|
||||||
{
|
|
||||||
ThrowIfNotStarted();
|
|
||||||
|
|
||||||
return _retainedMessagesManager.GetMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<MqttApplicationMessage> GetRetainedMessageAsync(string topic)
|
public Task<MqttApplicationMessage> GetRetainedMessageAsync(string topic)
|
||||||
{
|
{
|
||||||
if (topic == null)
|
if (topic == null)
|
||||||
@@ -222,6 +229,13 @@ namespace MQTTnet.Server
|
|||||||
return _retainedMessagesManager.GetMessage(topic);
|
return _retainedMessagesManager.GetMessage(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
|
||||||
|
{
|
||||||
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
|
return _retainedMessagesManager.GetMessages();
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IList<MqttSessionStatus>> GetSessionsAsync()
|
public Task<IList<MqttSessionStatus>> GetSessionsAsync()
|
||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
@@ -247,7 +261,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic))
|
if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic))
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Injected application messages must contain a topic. Topic alias is not supported.");
|
throw new NotSupportedException("Injected application messages must contain a topic (topic alias is not supported)");
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionItems = injectedApplicationMessage.CustomSessionItems ?? ServerSessionItems;
|
var sessionItems = injectedApplicationMessage.CustomSessionItems ?? ServerSessionItems;
|
||||||
@@ -263,6 +277,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
ThrowIfStarted();
|
ThrowIfStarted();
|
||||||
|
|
||||||
|
_isStopping = false;
|
||||||
|
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
var cancellationToken = _cancellationTokenSource.Token;
|
var cancellationToken = _cancellationTokenSource.Token;
|
||||||
|
|
||||||
@@ -278,11 +294,16 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Info("Started.");
|
_logger.Info("Started");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync(MqttServerStopOptions options)
|
||||||
{
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_cancellationTokenSource == null)
|
if (_cancellationTokenSource == null)
|
||||||
@@ -290,9 +311,11 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isStopping = true;
|
||||||
|
|
||||||
_cancellationTokenSource.Cancel(false);
|
_cancellationTokenSource.Cancel(false);
|
||||||
|
|
||||||
await _clientSessionsManager.CloseAllConnections().ConfigureAwait(false);
|
await _clientSessionsManager.CloseAllConnections(options.DefaultClientDisconnectOptions).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var adapter in _adapters)
|
foreach (var adapter in _adapters)
|
||||||
{
|
{
|
||||||
@@ -308,7 +331,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Info("Stopped.");
|
_logger.Info("Stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
|
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
|
||||||
@@ -362,14 +385,14 @@ 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)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
StopAsync().GetAwaiter().GetResult();
|
StopAsync(new MqttServerStopOptions()).GetAwaiter().GetResult();
|
||||||
|
|
||||||
foreach (var adapter in _adapters)
|
foreach (var adapter in _adapters)
|
||||||
{
|
{
|
||||||
@@ -382,6 +405,11 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
Task OnHandleClient(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
Task OnHandleClient(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (_isStopping || !AcceptNewConnections)
|
||||||
|
{
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
return _clientSessionsManager.HandleClientConnectionAsync(channelAdapter, cancellationToken);
|
return _clientSessionsManager.HandleClientConnectionAsync(channelAdapter, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,22 @@ using System.Threading.Tasks;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public static class MqttServerExtensions
|
public static class MqttServerExtensions
|
||||||
{
|
{
|
||||||
|
public static Task DisconnectClientAsync(this MqttServer server, string id, MqttDisconnectReasonCode reasonCode = MqttDisconnectReasonCode.NormalDisconnection)
|
||||||
|
{
|
||||||
|
if (server == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.DisconnectClientAsync(id, new MqttServerClientDisconnectOptions { ReasonCode = reasonCode });
|
||||||
|
}
|
||||||
|
|
||||||
public static Task InjectApplicationMessage(
|
public static Task InjectApplicationMessage(
|
||||||
this MqttServer server,
|
this MqttServer server,
|
||||||
string topic,
|
string topic,
|
||||||
@@ -47,6 +58,16 @@ namespace MQTTnet.Server
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task StopAsync(this MqttServer server)
|
||||||
|
{
|
||||||
|
if (server == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.StopAsync(new MqttServerStopOptions());
|
||||||
|
}
|
||||||
|
|
||||||
public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters)
|
public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters)
|
||||||
{
|
{
|
||||||
if (server == null)
|
if (server == null)
|
||||||
|
|||||||
@@ -131,6 +131,18 @@ namespace MQTTnet.Server
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Usually the MQTT packets can be send partially to improve memory allocations.
|
||||||
|
/// This is done by using multiple TCP packets or WebSocket frames etc.
|
||||||
|
/// Unfortunately not all clients do support this and will close the connection when receiving partial packets.
|
||||||
|
/// </summary>
|
||||||
|
public MqttServerOptionsBuilder WithoutPacketFragmentation()
|
||||||
|
{
|
||||||
|
_options.DefaultEndpointOptions.AllowPacketFragmentation = false;
|
||||||
|
_options.TlsEndpointOptions.AllowPacketFragmentation = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
|
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
|
||||||
{
|
{
|
||||||
_options.EnablePersistentSessions = value;
|
_options.EnablePersistentSessions = value;
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? KeepAlive { get; set; }
|
public bool? KeepAlive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
|
||||||
|
/// or WebSocket frames etc. Unfortunately not all clients do support this feature and
|
||||||
|
/// will close the connection when receiving such packets. If such clients are connecting to this
|
||||||
|
/// server the flag must be set to _false_.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowPacketFragmentation { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the TCP keep alive interval.
|
/// Gets or sets the TCP keep alive interval.
|
||||||
/// The value _null_ indicates that the OS and framework defaults should be used.
|
/// The value _null_ indicates that the OS and framework defaults should be used.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
@@ -18,41 +18,46 @@ namespace MQTTnet.Server
|
|||||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public long BytesReceived => _client.ChannelAdapter.BytesReceived;
|
||||||
/// Gets or sets the client identifier.
|
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
public long BytesSent => _client.ChannelAdapter.BytesSent;
|
||||||
/// </summary>
|
|
||||||
public string Id => _client.Id;
|
public DateTime ConnectedTimestamp => _client.Statistics.ConnectedTimestamp;
|
||||||
|
|
||||||
public string Endpoint => _client.Endpoint;
|
public string Endpoint => _client.Endpoint;
|
||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion => _client.ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
/// <summary>
|
||||||
|
/// Gets or sets the client identifier.
|
||||||
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
|
/// </summary>
|
||||||
|
public string Id => _client.Id;
|
||||||
|
|
||||||
public DateTime ConnectedTimestamp => _client.Statistics.ConnectedTimestamp;
|
public DateTime LastNonKeepAlivePacketReceivedTimestamp => _client.Statistics.LastNonKeepAlivePacketReceivedTimestamp;
|
||||||
|
|
||||||
public DateTime LastPacketReceivedTimestamp => _client.Statistics.LastPacketReceivedTimestamp;
|
public DateTime LastPacketReceivedTimestamp => _client.Statistics.LastPacketReceivedTimestamp;
|
||||||
|
|
||||||
public DateTime LastPacketSentTimestamp => _client.Statistics.LastPacketSentTimestamp;
|
public DateTime LastPacketSentTimestamp => _client.Statistics.LastPacketSentTimestamp;
|
||||||
|
|
||||||
public DateTime LastNonKeepAlivePacketReceivedTimestamp => _client.Statistics.LastNonKeepAlivePacketReceivedTimestamp;
|
public MqttProtocolVersion ProtocolVersion => _client.ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
||||||
|
|
||||||
public long ReceivedApplicationMessagesCount => _client.Statistics.ReceivedApplicationMessagesCount;
|
public long ReceivedApplicationMessagesCount => _client.Statistics.ReceivedApplicationMessagesCount;
|
||||||
|
|
||||||
public long SentApplicationMessagesCount => _client.Statistics.SentApplicationMessagesCount;
|
|
||||||
|
|
||||||
public long ReceivedPacketsCount => _client.Statistics.ReceivedPacketsCount;
|
public long ReceivedPacketsCount => _client.Statistics.ReceivedPacketsCount;
|
||||||
|
|
||||||
|
public long SentApplicationMessagesCount => _client.Statistics.SentApplicationMessagesCount;
|
||||||
|
|
||||||
public long SentPacketsCount => _client.Statistics.SentPacketsCount;
|
public long SentPacketsCount => _client.Statistics.SentPacketsCount;
|
||||||
|
|
||||||
public MqttSessionStatus Session { get; set; }
|
public MqttSessionStatus Session { get; set; }
|
||||||
|
|
||||||
public long BytesSent => _client.ChannelAdapter.BytesSent;
|
public Task DisconnectAsync(MqttServerClientDisconnectOptions options)
|
||||||
|
|
||||||
public long BytesReceived => _client.ChannelAdapter.BytesReceived;
|
|
||||||
|
|
||||||
public Task DisconnectAsync()
|
|
||||||
{
|
{
|
||||||
return _client.StopAsync(MqttDisconnectReasonCode.NormalDisconnection);
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _client.StopAsync(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetStatistics()
|
public void ResetStatistics()
|
||||||
@@ -60,4 +65,4 @@ namespace MQTTnet.Server
|
|||||||
_client.ResetStatistics();
|
_client.ResetStatistics();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public static class MqttClientStatusExtensions
|
||||||
|
{
|
||||||
|
static readonly MqttServerClientDisconnectOptions DefaultDisconnectOptions = new MqttServerClientDisconnectOptions
|
||||||
|
{
|
||||||
|
ReasonCode = MqttDisconnectReasonCode.NormalDisconnection,
|
||||||
|
ReasonString = null,
|
||||||
|
UserProperties = null,
|
||||||
|
ServerReference = null
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Task DisconnectAsync(this MqttClientStatus clientStatus)
|
||||||
|
{
|
||||||
|
if (clientStatus == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(clientStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientStatus.DisconnectAsync(DefaultDisconnectOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,10 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public DateTime CreatedTimestamp => _session.CreatedTimestamp;
|
public DateTime CreatedTimestamp => _session.CreatedTimestamp;
|
||||||
|
|
||||||
|
public DateTime? DisconnectedTimestamp => _session.DisconnectedTimestamp;
|
||||||
|
|
||||||
|
public uint ExpiryInterval => _session.ExpiryInterval;
|
||||||
|
|
||||||
public string Id => _session.Id;
|
public string Id => _session.Id;
|
||||||
|
|
||||||
public IDictionary Items => _session.Items;
|
public IDictionary Items => _session.Items;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttServerStopOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// These disconnect options are sent to every connected client via a DISCONNECT packet.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public MqttServerClientDisconnectOptions DefaultClientDisconnectOptions { get; set; } = new MqttServerClientDisconnectOptions
|
||||||
|
{
|
||||||
|
ReasonCode = MqttDisconnectReasonCode.ServerShuttingDown,
|
||||||
|
UserProperties = null,
|
||||||
|
ReasonString = null,
|
||||||
|
ServerReference = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MQTTnet.Server.Disconnecting;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class MqttServerStopOptionsBuilder
|
||||||
|
{
|
||||||
|
readonly MqttServerStopOptions _options = new MqttServerStopOptions();
|
||||||
|
|
||||||
|
public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(MqttServerClientDisconnectOptions value)
|
||||||
|
{
|
||||||
|
_options.DefaultClientDisconnectOptions = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(Action<MqttServerClientDisconnectOptionsBuilder> builder)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionsBuilder = new MqttServerClientDisconnectOptionsBuilder();
|
||||||
|
builder.Invoke(optionsBuilder);
|
||||||
|
|
||||||
|
_options.DefaultClientDisconnectOptions = optionsBuilder.Build();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttServerStopOptions Build()
|
||||||
|
{
|
||||||
|
return _options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user